PEP-0318, @decorator-style. In Guido's words:

"@ seems the syntax that everybody can hate equally"
Implementation by Mark Russell, from SF #979728.
This commit is contained in:
Anthony Baxter 2004-08-02 06:10:11 +00:00
parent fd7dc5169c
commit c2a5a63654
28 changed files with 2965 additions and 2335 deletions

View file

@ -73,6 +73,9 @@
\lineiii{Continue}{}{}
\hline
\lineiii{Decorators}{\member{nodes}}{List of function decorator expressions}
\hline
\lineiii{Dict}{\member{items}}{}
\hline
@ -101,7 +104,8 @@
\lineiii{}{\member{names}}{}
\hline
\lineiii{Function}{\member{name}}{name used in def, a string}
\lineiii{Function}{\member{decorators}}{\class{Decorators} or \code{None}}
\lineiii{}{\member{name}}{name used in def, a string}
\lineiii{}{\member{argnames}}{list of argument names, as strings}
\lineiii{}{\member{defaults}}{list of default values}
\lineiii{}{\member{flags}}{xxx}

View file

@ -109,10 +109,14 @@ def my_import(name):
\begin{verbatim}
class C:
@classmethod
def f(cls, arg1, arg2, ...): ...
f = classmethod(f)
\end{verbatim}
The \code{@classmethod} form is a function decorator -- see the description
of function definitions in chapter 7 of the
\citetitle[../ref/ref.html]{Python Reference Manual} for details.
It can be called either on the class (such as \code{C.f()}) or on an
instance (such as \code{C().f()}). The instance is ignored except for
its class.
@ -122,6 +126,7 @@ class C:
Class methods are different than \Cpp{} or Java static methods.
If you want those, see \function{staticmethod()} in this section.
\versionadded{2.2}
Function decorator syntax added in version 2.4.
\end{funcdesc}
\begin{funcdesc}{cmp}{x, y}
@ -936,10 +941,14 @@ except NameError:
\begin{verbatim}
class C:
@staticmethod
def f(arg1, arg2, ...): ...
f = staticmethod(f)
\end{verbatim}
The \code{@staticmethod} form is a function decorator -- see the description
of function definitions in chapter 7 of the
\citetitle[../ref/ref.html]{Python Reference Manual} for details.
It can be called either on the class (such as \code{C.f()}) or on an
instance (such as \code{C().f()}). The instance is ignored except
for its class.

View file

@ -315,8 +315,12 @@ section~\ref{types}):
\begin{productionlist}
\production{funcdef}
{"def" \token{funcname} "(" [\token{parameter_list}] ")"
{[\token{decorators}] "def" \token{funcname} "(" [\token{parameter_list}] ")"
":" \token{suite}}
\production{decorators}
{\token{decorator} ([NEWLINE] \token{decorator})* NEWLINE}
\production{decorator}
{"@" \token{dotted_name} ["(" [\token{argument_list} [","]] ")"]}
\production{parameter_list}
{(\token{defparameter} ",")*}
\productioncont{("*" \token{identifier} [, "**" \token{identifier}]}
@ -343,6 +347,27 @@ as the global namespace to be used when the function is called.
The function definition does not execute the function body; this gets
executed only when the function is called.
A function definition may be wrapped by one or more decorator expressions.
Decorator expressions are evaluated when the function is defined, in the scope
that contains the function definition. The result must be a callable,
which is invoked with the function object as the only argument.
The returned value is bound to the function name instead of the function
object. If there are multiple decorators, they are applied in reverse
order. For example, the following code:
\begin{verbatim}
@f1
@f2
def func(): pass
\end{verbatim}
is equivalent to:
\begin{verbatim}
def func(): pass
func = f2(f1(func))
\end{verbatim}
When one or more top-level parameters have the form \var{parameter}
\code{=} \var{expression}, the function is said to have ``default
parameter values.'' For a parameter with a

View file

@ -28,7 +28,9 @@ single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
file_input: (NEWLINE | stmt)* ENDMARKER
eval_input: testlist NEWLINE* ENDMARKER
funcdef: 'def' NAME parameters ':' suite
decorator: '@' dotted_name [ '(' [arglist] ')' ]
decorators: decorator ([NEWLINE] decorator)* NEWLINE
funcdef: [decorators] 'def' NAME parameters ':' suite
parameters: '(' [varargslist] ')'
varargslist: (fpdef ['=' test] ',')* ('*' NAME [',' '**' NAME] | '**' NAME) | fpdef ['=' test] (',' fpdef ['=' test])* [',']
fpdef: NAME | '(' fplist ')'

View file

@ -1,72 +1,74 @@
#define single_input 256
#define file_input 257
#define eval_input 258
#define funcdef 259
#define parameters 260
#define varargslist 261
#define fpdef 262
#define fplist 263
#define stmt 264
#define simple_stmt 265
#define small_stmt 266
#define expr_stmt 267
#define augassign 268
#define print_stmt 269
#define del_stmt 270
#define pass_stmt 271
#define flow_stmt 272
#define break_stmt 273
#define continue_stmt 274
#define return_stmt 275
#define yield_stmt 276
#define raise_stmt 277
#define import_stmt 278
#define import_as_name 279
#define dotted_as_name 280
#define dotted_name 281
#define global_stmt 282
#define exec_stmt 283
#define assert_stmt 284
#define compound_stmt 285
#define if_stmt 286
#define while_stmt 287
#define for_stmt 288
#define try_stmt 289
#define except_clause 290
#define suite 291
#define test 292
#define and_test 293
#define not_test 294
#define comparison 295
#define comp_op 296
#define expr 297
#define xor_expr 298
#define and_expr 299
#define shift_expr 300
#define arith_expr 301
#define term 302
#define factor 303
#define power 304
#define atom 305
#define listmaker 306
#define testlist_gexp 307
#define lambdef 308
#define trailer 309
#define subscriptlist 310
#define subscript 311
#define sliceop 312
#define exprlist 313
#define testlist 314
#define testlist_safe 315
#define dictmaker 316
#define classdef 317
#define arglist 318
#define argument 319
#define list_iter 320
#define list_for 321
#define list_if 322
#define gen_iter 323
#define gen_for 324
#define gen_if 325
#define testlist1 326
#define encoding_decl 327
#define decorator 259
#define decorators 260
#define funcdef 261
#define parameters 262
#define varargslist 263
#define fpdef 264
#define fplist 265
#define stmt 266
#define simple_stmt 267
#define small_stmt 268
#define expr_stmt 269
#define augassign 270
#define print_stmt 271
#define del_stmt 272
#define pass_stmt 273
#define flow_stmt 274
#define break_stmt 275
#define continue_stmt 276
#define return_stmt 277
#define yield_stmt 278
#define raise_stmt 279
#define import_stmt 280
#define import_as_name 281
#define dotted_as_name 282
#define dotted_name 283
#define global_stmt 284
#define exec_stmt 285
#define assert_stmt 286
#define compound_stmt 287
#define if_stmt 288
#define while_stmt 289
#define for_stmt 290
#define try_stmt 291
#define except_clause 292
#define suite 293
#define test 294
#define and_test 295
#define not_test 296
#define comparison 297
#define comp_op 298
#define expr 299
#define xor_expr 300
#define and_expr 301
#define shift_expr 302
#define arith_expr 303
#define term 304
#define factor 305
#define power 306
#define atom 307
#define listmaker 308
#define testlist_gexp 309
#define lambdef 310
#define trailer 311
#define subscriptlist 312
#define subscript 313
#define sliceop 314
#define exprlist 315
#define testlist 316
#define testlist_safe 317
#define dictmaker 318
#define classdef 319
#define arglist 320
#define argument 321
#define list_iter 322
#define list_for 323
#define list_if 324
#define gen_iter 325
#define gen_for 326
#define gen_if 327
#define testlist1 328
#define encoding_decl 329

View file

@ -22,7 +22,9 @@ PyAPI_FUNC(void) PyNode_Free(node *n);
/* Node access functions */
#define NCH(n) ((n)->n_nchildren)
#define CHILD(n, i) (&(n)->n_child[i])
#define RCHILD(n, i) (CHILD(n, NCH(n) + i))
#define TYPE(n) ((n)->n_type)
#define STR(n) ((n)->n_str)

View file

@ -57,10 +57,11 @@ extern "C" {
#define DOUBLESTAREQUAL 47
#define DOUBLESLASH 48
#define DOUBLESLASHEQUAL 49
#define AT 50
/* Don't forget to update the table _PyParser_TokenNames in tokenizer.c! */
#define OP 50
#define ERRORTOKEN 51
#define N_TOKENS 52
#define OP 51
#define ERRORTOKEN 52
#define N_TOKENS 53
/* Special definitions for cooperation with parser */

File diff suppressed because it is too large Load diff

View file

@ -366,6 +366,13 @@ def visitLambda(self, node):
self._visitFuncOrLambda(node, isLambda=1)
def _visitFuncOrLambda(self, node, isLambda=0):
if not isLambda and node.decorators:
for decorator in reversed(node.decorators.nodes):
self.visit(decorator)
ndecorators = len(node.decorators.nodes)
else:
ndecorators = 0
gen = self.FunctionGen(node, self.scopes, isLambda,
self.class_name, self.get_module())
walk(node.code, gen)
@ -382,6 +389,9 @@ def _visitFuncOrLambda(self, node, isLambda=0):
else:
self.emit('LOAD_CONST', gen)
self.emit('MAKE_FUNCTION', len(node.defaults))
for i in range(ndecorators):
self.emit('CALL_FUNCTION', 1)
def visitClass(self, node):
gen = self.ClassGen(node, self.scopes,

View file

@ -224,6 +224,8 @@ def visitModule(self, node):
visitExpression = visitModule
def visitFunction(self, node, parent):
if node.decorators:
self.visit(node.decorators, parent)
parent.add_def(node.name)
for n in node.defaults:
self.visit(n, parent)

View file

@ -185,29 +185,81 @@ def eval_input(self, nodelist):
### is this sufficient?
return Expression(self.com_node(nodelist[0]))
def decorator_name(self, nodelist):
listlen = len(nodelist)
assert listlen >= 1 and listlen % 2 == 1
item = self.atom_name(nodelist)
i = 1
while i < listlen:
assert nodelist[i][0] == token.DOT
assert nodelist[i + 1][0] == token.NAME
item = Getattr(item, nodelist[i + 1][1])
i += 2
return item
def decorator(self, nodelist):
# '@' dotted_name [ '(' [arglist] ')' ]
assert len(nodelist) in (2, 4, 5)
assert nodelist[0][0] == token.AT
assert nodelist[1][0] == symbol.dotted_name
funcname = self.decorator_name(nodelist[1][1:])
if len(nodelist) > 2:
assert nodelist[2][0] == token.LPAR
expr = self.com_call_function(funcname, nodelist[3])
else:
expr = funcname
return expr
def decorators(self, nodelist):
# decorators: decorator ([NEWLINE] decorator)* NEWLINE
listlen = len(nodelist)
i = 0
items = []
while i < listlen:
assert nodelist[i][0] == symbol.decorator
items.append(self.decorator(nodelist[i][1:]))
i += 1
if i < listlen and nodelist[i][0] == token.NEWLINE:
i += 1
return Decorators(items)
def funcdef(self, nodelist):
# funcdef: 'def' NAME parameters ':' suite
# -6 -5 -4 -3 -2 -1
# funcdef: [decorators] 'def' NAME parameters ':' suite
# parameters: '(' [varargslist] ')'
lineno = nodelist[1][2]
name = nodelist[1][1]
args = nodelist[2][2]
if len(nodelist) == 6:
assert nodelist[0][0] == symbol.decorators
decorators = self.decorators(nodelist[0][1:])
else:
assert len(nodelist) == 5
decorators = None
lineno = nodelist[-4][2]
name = nodelist[-4][1]
args = nodelist[-3][2]
if args[0] == symbol.varargslist:
names, defaults, flags = self.com_arglist(args[1:])
else:
names = defaults = ()
flags = 0
doc = self.get_docstring(nodelist[4])
doc = self.get_docstring(nodelist[-1])
# code for function
code = self.com_node(nodelist[4])
code = self.com_node(nodelist[-1])
if doc is not None:
assert isinstance(code, Stmt)
assert isinstance(code.nodes[0], Discard)
del code.nodes[0]
n = Function(name, names, defaults, flags, doc, code)
n = Function(decorators, name, names, defaults, flags, doc, code)
n.lineno = lineno
return n

View file

@ -222,7 +222,7 @@ def _readmodule(module, path, inpackage=None):
else:
super.append(token)
inherit = names
cur_class = Class(module, class_name, inherit, file, lineno)
cur_class = Class(fullmodule, class_name, inherit, file, lineno)
if not stack:
dict[class_name] = cur_class
stack.append((cur_class, thisindent))

View file

@ -13,75 +13,77 @@
single_input = 256
file_input = 257
eval_input = 258
funcdef = 259
parameters = 260
varargslist = 261
fpdef = 262
fplist = 263
stmt = 264
simple_stmt = 265
small_stmt = 266
expr_stmt = 267
augassign = 268
print_stmt = 269
del_stmt = 270
pass_stmt = 271
flow_stmt = 272
break_stmt = 273
continue_stmt = 274
return_stmt = 275
yield_stmt = 276
raise_stmt = 277
import_stmt = 278
import_as_name = 279
dotted_as_name = 280
dotted_name = 281
global_stmt = 282
exec_stmt = 283
assert_stmt = 284
compound_stmt = 285
if_stmt = 286
while_stmt = 287
for_stmt = 288
try_stmt = 289
except_clause = 290
suite = 291
test = 292
and_test = 293
not_test = 294
comparison = 295
comp_op = 296
expr = 297
xor_expr = 298
and_expr = 299
shift_expr = 300
arith_expr = 301
term = 302
factor = 303
power = 304
atom = 305
listmaker = 306
testlist_gexp = 307
lambdef = 308
trailer = 309
subscriptlist = 310
subscript = 311
sliceop = 312
exprlist = 313
testlist = 314
testlist_safe = 315
dictmaker = 316
classdef = 317
arglist = 318
argument = 319
list_iter = 320
list_for = 321
list_if = 322
gen_iter = 323
gen_for = 324
gen_if = 325
testlist1 = 326
encoding_decl = 327
decorator = 259
decorators = 260
funcdef = 261
parameters = 262
varargslist = 263
fpdef = 264
fplist = 265
stmt = 266
simple_stmt = 267
small_stmt = 268
expr_stmt = 269
augassign = 270
print_stmt = 271
del_stmt = 272
pass_stmt = 273
flow_stmt = 274
break_stmt = 275
continue_stmt = 276
return_stmt = 277
yield_stmt = 278
raise_stmt = 279
import_stmt = 280
import_as_name = 281
dotted_as_name = 282
dotted_name = 283
global_stmt = 284
exec_stmt = 285
assert_stmt = 286
compound_stmt = 287
if_stmt = 288
while_stmt = 289
for_stmt = 290
try_stmt = 291
except_clause = 292
suite = 293
test = 294
and_test = 295
not_test = 296
comparison = 297
comp_op = 298
expr = 299
xor_expr = 300
and_expr = 301
shift_expr = 302
arith_expr = 303
term = 304
factor = 305
power = 306
atom = 307
listmaker = 308
testlist_gexp = 309
lambdef = 310
trailer = 311
subscriptlist = 312
subscript = 313
sliceop = 314
exprlist = 315
testlist = 316
testlist_safe = 317
dictmaker = 318
classdef = 319
arglist = 320
argument = 321
list_iter = 322
list_for = 323
list_if = 324
gen_iter = 325
gen_for = 326
gen_if = 327
testlist1 = 328
encoding_decl = 329
#--end constants--
sym_name = {}

View file

@ -645,4 +645,15 @@ test_tokenize
174,29-174,30: OP ')'
174,30-174,31: NEWLINE '\n'
175,0-175,1: NL '\n'
176,0-176,0: ENDMARKER ''
176,0-176,1: OP '@'
176,1-176,13: NAME 'staticmethod'
176,13-176,14: NEWLINE '\n'
177,0-177,3: NAME 'def'
177,4-177,7: NAME 'foo'
177,7-177,8: OP '('
177,8-177,9: OP ')'
177,9-177,10: OP ':'
177,11-177,15: NAME 'pass'
177,15-177,16: NEWLINE '\n'
178,0-178,1: NL '\n'
179,0-179,0: ENDMARKER ''

33
Lib/test/pyclbr_input.py Normal file
View file

@ -0,0 +1,33 @@
"""Test cases for test_pyclbr.py"""
def f(): pass
class Other(object):
@classmethod
def foo(c): pass
def om(self): pass
class B (object):
def bm(self): pass
class C (B):
foo = Other().foo
om = Other.om
d = 10
# XXX: This causes test_pyclbr.py to fail, but only because the
# introspection-based is_method() code in the test can't
# distinguish between this and a geniune method function like m().
# The pyclbr.py module gets this right as it parses the text.
#
#f = f
def m(self): pass
@staticmethod
def sm(self): pass
@classmethod
def cm(self): pass

194
Lib/test/test_decorators.py Normal file
View file

@ -0,0 +1,194 @@
import unittest
from test import test_support
def funcattrs(**kwds):
def decorate(func):
func.__dict__.update(kwds)
return func
return decorate
class MiscDecorators (object):
@staticmethod
def author(name):
def decorate(func):
func.__dict__['author'] = name
return func
return decorate
# -----------------------------------------------
class DbcheckError (Exception):
def __init__(self, exprstr, func, args, kwds):
# A real version of this would set attributes here
Exception.__init__(self, "dbcheck %r failed (func=%s args=%s kwds=%s)" %
(exprstr, func, args, kwds))
def dbcheck(exprstr, globals=None, locals=None):
"Decorator to implement debugging assertions"
def decorate(func):
expr = compile(exprstr, "dbcheck-%s" % func.func_name, "eval")
def check(*args, **kwds):
if not eval(expr, globals, locals):
raise DbcheckError(exprstr, func, args, kwds)
return func(*args, **kwds)
return check
return decorate
# -----------------------------------------------
def countcalls(counts):
"Decorator to count calls to a function"
def decorate(func):
name = func.func_name
counts[name] = 0
def call(*args, **kwds):
counts[name] += 1
return func(*args, **kwds)
# XXX: Would like to say: call.func_name = func.func_name here
# to make nested decorators work in any order, but func_name
# is a readonly attribute
return call
return decorate
# -----------------------------------------------
def memoize(func):
saved = {}
def call(*args):
try:
return saved[args]
except KeyError:
res = func(*args)
saved[args] = res
return res
except TypeError:
# Unhashable argument
return func(*args)
return call
# -----------------------------------------------
class TestDecorators(unittest.TestCase):
def test_single(self):
class C(object):
@staticmethod
def foo(): return 42
self.assertEqual(C.foo(), 42)
self.assertEqual(C().foo(), 42)
def test_dotted(self):
decorators = MiscDecorators()
@decorators.author('Cleese')
def foo(): return 42
self.assertEqual(foo(), 42)
self.assertEqual(foo.author, 'Cleese')
def test_argforms(self):
# A few tests of argument passing, as we use restricted form
# of expressions for decorators.
def noteargs(*args, **kwds):
def decorate(func):
setattr(func, 'dbval', (args, kwds))
return func
return decorate
args = ( 'Now', 'is', 'the', 'time' )
kwds = dict(one=1, two=2)
@noteargs(*args, **kwds)
def f1(): return 42
self.assertEqual(f1(), 42)
self.assertEqual(f1.dbval, (args, kwds))
@noteargs('terry', 'gilliam', eric='idle', john='cleese')
def f2(): return 84
self.assertEqual(f2(), 84)
self.assertEqual(f2.dbval, (('terry', 'gilliam'),
dict(eric='idle', john='cleese')))
@noteargs(1, 2,)
def f3(): pass
self.assertEqual(f3.dbval, ((1, 2), {}))
def test_dbcheck(self):
@dbcheck('args[1] is not None')
def f(a, b):
return a + b
self.assertEqual(f(1, 2), 3)
self.assertRaises(DbcheckError, f, 1, None)
def test_memoize(self):
# XXX: This doesn't work unless memoize is the last decorator -
# see the comment in countcalls.
counts = {}
@countcalls(counts) @memoize
def double(x):
return x * 2
self.assertEqual(counts, dict(double=0))
# Only the first call with a given argument bumps the call count:
#
self.assertEqual(double(2), 4)
self.assertEqual(counts['double'], 1)
self.assertEqual(double(2), 4)
self.assertEqual(counts['double'], 1)
self.assertEqual(double(3), 6)
self.assertEqual(counts['double'], 2)
# Unhashable arguments do not get memoized:
#
self.assertEqual(double([10]), [10, 10])
self.assertEqual(counts['double'], 3)
self.assertEqual(double([10]), [10, 10])
self.assertEqual(counts['double'], 4)
def test_errors(self):
# Test syntax restrictions - these are all compile-time errors:
#
for expr in [ "1+2", "x[3]", "(1, 2)" ]:
# Sanity check: is expr is a valid expression by itself?
compile(expr, "testexpr", "exec")
codestr = "@%s\ndef f(): pass" % expr
self.assertRaises(SyntaxError, compile, codestr, "test", "exec")
# Test runtime errors
def unimp(func):
raise NotImplementedError
context = dict(nullval=None, unimp=unimp)
for expr, exc in [ ("undef", NameError),
("nullval", TypeError),
("nullval.attr", AttributeError),
("unimp", NotImplementedError)]:
codestr = "@%s\ndef f(): pass\nassert f() is None" % expr
code = compile(codestr, "test", "exec")
self.assertRaises(exc, eval, code, context)
def test_double(self):
class C(object):
@funcattrs(abc=1, xyz="haha")
@funcattrs(booh=42)
def foo(self): return 42
self.assertEqual(C().foo(), 42)
self.assertEqual(C.foo.abc, 1)
self.assertEqual(C.foo.xyz, "haha")
self.assertEqual(C.foo.booh, 42)
def test_order(self):
class C(object):
@funcattrs(abc=1) @staticmethod
def foo(): return 42
# This wouldn't work if staticmethod was called first
self.assertEqual(C.foo(), 42)
self.assertEqual(C().foo(), 42)
def test_main():
test_support.run_unittest(TestDecorators)
if __name__=="__main__":
test_main()

View file

@ -15,8 +15,8 @@ def roundtrip(self, f, s):
t = st1.totuple()
try:
st2 = parser.sequence2st(t)
except parser.ParserError:
self.fail("could not roundtrip %r" % s)
except parser.ParserError, why:
self.fail("could not roundtrip %r: %s" % (s, why))
self.assertEquals(t, st2.totuple(),
"could not re-generate syntax tree")
@ -119,6 +119,14 @@ def test_function_defs(self):
self.check_suite("def f(a, b, foo=bar, *args, **kw): pass")
self.check_suite("def f(a, b, foo=bar, **kw): pass")
self.check_suite("@staticmethod\n"
"def f(): pass")
self.check_suite("@staticmethod\n"
"@funcattrs(x, y)\n"
"def f(): pass")
self.check_suite("@funcattrs()\n"
"def f(): pass")
def test_import_from_statement(self):
self.check_suite("from sys.path import *")
self.check_suite("from sys.path import dirname")

View file

@ -8,6 +8,9 @@
import pyclbr
from unittest import TestCase
StaticMethodType = type(staticmethod(lambda: None))
ClassMethodType = type(classmethod(lambda c: None))
# This next line triggers an error on old versions of pyclbr.
from commands import getstatus
@ -43,11 +46,10 @@ def assertHaskey(self, obj, key, ignore):
print >>sys.stderr, "***",key
self.failUnless(obj.has_key(key))
def assertEquals(self, a, b, ignore=None):
def assertEqualsOrIgnored(self, a, b, ignore):
''' succeed iff a == b or a in ignore or b in ignore '''
if (ignore == None) or (a in ignore) or (b in ignore): return
unittest.TestCase.assertEquals(self, a, b)
if a not in ignore and b not in ignore:
self.assertEquals(a, b)
def checkModule(self, moduleName, module=None, ignore=()):
''' succeed iff pyclbr.readmodule_ex(modulename) corresponds
@ -62,11 +64,22 @@ def checkModule(self, moduleName, module=None, ignore=()):
dict = pyclbr.readmodule_ex(moduleName)
def ismethod(obj, name):
if not isinstance(obj, MethodType):
return False
if obj.im_self is not None:
return False
def ismethod(oclass, obj, name):
classdict = oclass.__dict__
if isinstance(obj, FunctionType):
if not isinstance(classdict[name], StaticMethodType):
return False
else:
if not isinstance(obj, MethodType):
return False
if obj.im_self is not None:
if (not isinstance(classdict[name], ClassMethodType) or
obj.im_self is not oclass):
return False
else:
if not isinstance(classdict[name], FunctionType):
return False
objname = obj.__name__
if objname.startswith("__") and not objname.endswith("__"):
objname = "_%s%s" % (obj.im_class.__name__, objname)
@ -81,7 +94,7 @@ def ismethod(obj, name):
if isinstance(value, pyclbr.Function):
self.assertEquals(type(py_item), FunctionType)
else:
self.assertEquals(type(py_item), ClassType)
self.failUnless(isinstance(py_item, (ClassType, type)))
real_bases = [base.__name__ for base in py_item.__bases__]
pyclbr_bases = [ getattr(base, 'name', base)
for base in value.super ]
@ -94,7 +107,7 @@ def ismethod(obj, name):
actualMethods = []
for m in py_item.__dict__.keys():
if ismethod(getattr(py_item, m), m):
if ismethod(py_item, getattr(py_item, m), m):
actualMethods.append(m)
foundMethods = []
for m in value.methods.keys():
@ -107,7 +120,8 @@ def ismethod(obj, name):
self.assertListEq(foundMethods, actualMethods, ignore)
self.assertEquals(py_item.__module__, value.module)
self.assertEquals(py_item.__name__, value.name, ignore)
self.assertEqualsOrIgnored(py_item.__name__, value.name,
ignore)
# can't check file or lineno
except:
print >>sys.stderr, "class=%s" % py_item
@ -132,6 +146,12 @@ def test_easy(self):
self.checkModule('rfc822')
self.checkModule('difflib')
def test_decorators(self):
# XXX: See comment in pyclbr_input.py for a test that would fail
# if it were not commented out.
#
self.checkModule('test.pyclbr_input')
def test_others(self):
cm = self.checkModule

View file

@ -173,3 +173,6 @@ x = -1*1/1 + 1*1 - ---1*1
import sys, time
x = sys.modules['time'].time()
@staticmethod
def foo(): pass

View file

@ -60,9 +60,10 @@ RIGHTSHIFTEQUAL = 46
DOUBLESTAREQUAL = 47
DOUBLESLASH = 48
DOUBLESLASHEQUAL = 49
OP = 50
ERRORTOKEN = 51
N_TOKENS = 52
AT = 50
OP = 51
ERRORTOKEN = 52
N_TOKENS = 53
NT_OFFSET = 256
#--end constants--

View file

@ -83,7 +83,7 @@ def maybe(*choices): return group(*choices) + '?'
r"~")
Bracket = '[][(){}]'
Special = group(r'\r?\n', r'[:;.,`]')
Special = group(r'\r?\n', r'[:;.,`@]')
Funny = group(Operator, Bracket, Special)
PlainToken = group(Number, Funny, String, Name)

View file

@ -824,6 +824,7 @@ static int validate_terminal(node *terminal, int type, char *string);
#define validate_vbar(ch) validate_terminal(ch, VBAR, "|")
#define validate_doublestar(ch) validate_terminal(ch, DOUBLESTAR, "**")
#define validate_dot(ch) validate_terminal(ch, DOT, ".")
#define validate_at(ch) validate_terminal(ch, AT, "@")
#define validate_name(ch, str) validate_terminal(ch, NAME, str)
#define VALIDATER(n) static int validate_##n(node *tree)
@ -2362,20 +2363,72 @@ validate_testlist_gexp(node *tree)
return ok;
}
/* decorator:
* '@' dotted_name [ '(' [arglist] ')' ]
*/
static int
validate_decorator(node *tree)
{
int ok;
int nch = NCH(tree);
ok = (validate_ntype(tree, decorator) &&
(nch == 2 || nch == 4 || nch == 5) &&
validate_at(CHILD(tree, 0)) &&
validate_dotted_name(CHILD(tree, 1)));
if (ok && nch != 2) {
ok = (validate_lparen(CHILD(tree, 2)) &&
validate_rparen(RCHILD(tree, -1)));
if (ok && nch == 5)
ok = validate_arglist(CHILD(tree, 3));
}
return ok;
}
/* decorators:
* decorator ([NEWLINE] decorator)* NEWLINE
*/
static int
validate_decorators(node *tree)
{
int i, nch, ok;
nch = NCH(tree);
ok = validate_ntype(tree, decorators) && nch >= 2;
i = 0;
while (ok && i < nch - 1) {
ok = validate_decorator(CHILD(tree, i));
if (TYPE(CHILD(tree, i + 1)) == NEWLINE)
++i;
++i;
}
return ok;
}
/* funcdef:
* 'def' NAME parameters ':' suite
*
*
* -6 -5 -4 -3 -2 -1
* [decorators] 'def' NAME parameters ':' suite
*/
static int
validate_funcdef(node *tree)
{
return (validate_ntype(tree, funcdef)
&& validate_numnodes(tree, 5, "funcdef")
&& validate_name(CHILD(tree, 0), "def")
&& validate_ntype(CHILD(tree, 1), NAME)
&& validate_colon(CHILD(tree, 3))
&& validate_parameters(CHILD(tree, 2))
&& validate_suite(CHILD(tree, 4)));
int nch = NCH(tree);
int ok = (validate_ntype(tree, funcdef)
&& ((nch == 5) || (nch == 6))
&& validate_name(RCHILD(tree, -5), "def")
&& validate_ntype(RCHILD(tree, -4), NAME)
&& validate_colon(RCHILD(tree, -2))
&& validate_parameters(RCHILD(tree, -3))
&& validate_suite(RCHILD(tree, -1)));
if (ok && (nch == 6))
ok = validate_decorators(CHILD(tree, 0));
return ok;
}

View file

@ -92,6 +92,7 @@ char *_PyParser_TokenNames[] = {
"DOUBLESTAREQUAL",
"DOUBLESLASH",
"DOUBLESLASHEQUAL",
"AT",
/* This table must match the #defines in token.h! */
"OP",
"<ERRORTOKEN>",
@ -847,6 +848,7 @@ PyToken_OneChar(int c)
case '}': return RBRACE;
case '^': return CIRCUMFLEX;
case '~': return TILDE;
case '@': return AT;
default: return OP;
}
}

View file

@ -1876,6 +1876,7 @@ com_testlist_gexp(struct compiling *c, node *n)
else com_list(c, n, 0);
}
static void
com_dictmaker(struct compiling *c, node *n)
{
@ -3963,8 +3964,9 @@ com_argdefs(struct compiling *c, node *n)
n = CHILD(n, 1);
}
else {
REQ(n, funcdef); /* funcdef: 'def' NAME parameters ... */
n = CHILD(n, 2);
REQ(n, funcdef);
/* funcdef: [decorators] 'def' NAME parameters ':' suite */
n = RCHILD(n, -3);
REQ(n, parameters); /* parameters: '(' [varargslist] ')' */
n = CHILD(n, 1);
}
@ -4008,16 +4010,90 @@ com_argdefs(struct compiling *c, node *n)
return ndefs;
}
static void
com_decorator_name(struct compiling *c, node *n)
{
/* dotted_name: NAME ('.' NAME)* */
int i, nch;
node *varname;
REQ(n, dotted_name);
nch = NCH(n);
assert(nch >= 1 && nch % 2 == 1);
varname = CHILD(n, 0);
REQ(varname, NAME);
com_addop_varname(c, VAR_LOAD, STR(varname));
for (i = 1; i < nch; i += 2) {
node *attrname;
REQ(CHILD(n, i), DOT);
attrname = CHILD(n, i + 1);
REQ(attrname, NAME);
com_addop_name(c, LOAD_ATTR, STR(attrname));
}
}
static void
com_decorator(struct compiling *c, node *n)
{
/* decorator: '@' dotted_name [ '(' [arglist] ')' ] */
int nch = NCH(n);
assert(nch >= 2);
REQ(CHILD(n, 0), AT);
com_decorator_name(c, CHILD(n, 1));
if (nch > 2) {
assert(nch == 4 || nch == 5);
REQ(CHILD(n, 2), LPAR);
REQ(CHILD(n, nch - 1), RPAR);
com_call_function(c, CHILD(n, 3));
}
}
static int
com_decorators(struct compiling *c, node *n)
{
int i, nch, ndecorators;
/* decorator ([NEWLINE] decorator)* NEWLINE */
nch = NCH(n);
assert(nch >= 2);
REQ(CHILD(n, nch - 1), NEWLINE);
ndecorators = 0;
for (i = NCH(n) - 1; i >= 0; --i) {
node *ch = CHILD(n, i);
if (TYPE(ch) != NEWLINE) {
com_decorator(c, ch);
++ndecorators;
}
}
return ndecorators;
}
static void
com_funcdef(struct compiling *c, node *n)
{
PyObject *co;
int ndefs;
REQ(n, funcdef); /* funcdef: 'def' NAME parameters ':' suite */
int ndefs, ndecorators;
REQ(n, funcdef);
/* -6 -5 -4 -3 -2 -1
funcdef: [decorators] 'def' NAME parameters ':' suite */
if (NCH(n) == 6)
ndecorators = com_decorators(c, CHILD(n, 0));
else
ndecorators = 0;
ndefs = com_argdefs(c, n);
if (ndefs < 0)
return;
symtable_enter_scope(c->c_symtable, STR(CHILD(n, 1)), TYPE(n),
symtable_enter_scope(c->c_symtable, STR(RCHILD(n, -4)), TYPE(n),
n->n_lineno);
co = (PyObject *)icompile(n, c);
symtable_exit_scope(c->c_symtable);
@ -4033,7 +4109,12 @@ com_funcdef(struct compiling *c, node *n)
else
com_addoparg(c, MAKE_FUNCTION, ndefs);
com_pop(c, ndefs);
com_addop_varname(c, VAR_STORE, STR(CHILD(n, 1)));
while (ndecorators > 0) {
com_addoparg(c, CALL_FUNCTION, 1);
com_pop(c, 1);
ndecorators--;
}
com_addop_varname(c, VAR_STORE, STR(RCHILD(n, -4)));
com_pop(c, 1);
Py_DECREF(co);
}
@ -4112,7 +4193,7 @@ com_node(struct compiling *c, node *n)
switch (TYPE(n)) {
/* Definition nodes */
case funcdef:
com_funcdef(c, n);
break;
@ -4377,21 +4458,23 @@ compile_funcdef(struct compiling *c, node *n)
{
PyObject *doc;
node *ch;
REQ(n, funcdef); /* funcdef: 'def' NAME parameters ':' suite */
c->c_name = STR(CHILD(n, 1));
doc = get_docstring(c, CHILD(n, 4));
REQ(n, funcdef);
/* -6 -5 -4 -3 -2 -1
funcdef: [decorators] 'def' NAME parameters ':' suite */
c->c_name = STR(RCHILD(n, -4));
doc = get_docstring(c, RCHILD(n, -1));
if (doc != NULL) {
(void) com_addconst(c, doc);
Py_DECREF(doc);
}
else
(void) com_addconst(c, Py_None); /* No docstring */
ch = CHILD(n, 2); /* parameters: '(' [varargslist] ')' */
ch = RCHILD(n, -3); /* parameters: '(' [varargslist] ')' */
ch = CHILD(ch, 1); /* ')' | varargslist */
if (TYPE(ch) == varargslist)
com_arglist(c, ch);
c->c_infunction = 1;
com_node(c, CHILD(n, 4));
com_node(c, RCHILD(n, -1));
c->c_infunction = 0;
com_strip_lnotab(c);
com_addoparg(c, LOAD_CONST, com_addconst(c, Py_None));
@ -5587,9 +5670,12 @@ symtable_node(struct symtable *st, node *n)
loop:
switch (TYPE(n)) {
case funcdef: {
char *func_name = STR(CHILD(n, 1));
char *func_name;
if (NCH(n) == 6)
symtable_node(st, CHILD(n, 0));
func_name = STR(RCHILD(n, -4));
symtable_add_def(st, func_name, DEF_LOCAL);
symtable_default_args(st, CHILD(n, 2));
symtable_default_args(st, RCHILD(n, -3));
symtable_enter_scope(st, func_name, TYPE(n), n->n_lineno);
symtable_funcdef(st, n);
symtable_exit_scope(st);
@ -5734,6 +5820,17 @@ symtable_node(struct symtable *st, node *n)
to be coded with great care, even though they look like
rather innocuous. Each case must double-check TYPE(n).
*/
case decorator:
if (TYPE(n) == decorator) {
/* decorator: '@' dotted_name [ '(' [arglist] ')' ] */
node *name, *varname;
name = CHILD(n, 1);
REQ(name, dotted_name);
varname = CHILD(name, 0);
REQ(varname, NAME);
symtable_add_use(st, STR(varname));
}
/* fall through */
case argument:
if (TYPE(n) == argument && NCH(n) == 3) {
n = CHILD(n, 2);
@ -5787,7 +5884,7 @@ symtable_funcdef(struct symtable *st, node *n)
if (NCH(n) == 4)
symtable_params(st, CHILD(n, 1));
} else
symtable_params(st, CHILD(n, 2));
symtable_params(st, RCHILD(n, -3));
body = CHILD(n, NCH(n) - 1);
symtable_node(st, body);
}

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,8 @@
# = ... a default value for the node constructor (optional args)
Module: doc*, node
Stmt: nodes!
Function: name*, argnames*, defaults!, flags*, doc*, code
Decorators: nodes!
Function: decorators&, name*, argnames*, defaults!, flags*, doc*, code
Lambda: argnames*, defaults!, flags*, code
Class: name*, bases!, doc*, code
Pass:

View file

@ -154,19 +154,19 @@ def _gen_getChildNodes(self, buf):
else:
print >> buf, " return %s" % COMMA.join(clist)
else:
print >> buf, " nodes = []"
template = " nodes.%s(%sself.%s%s)"
print >> buf, " nodelist = []"
template = " nodelist.%s(%sself.%s%s)"
for name in self.argnames:
if self.argprops[name] == P_NONE:
tmp = (" if self.%s is not None:"
" nodes.append(self.%s)")
" nodelist.append(self.%s)")
print >> buf, tmp % (name, name)
elif self.argprops[name] == P_NESTED:
print >> buf, template % ("extend", "flatten_nodes(",
name, ")")
elif self.argprops[name] == P_NODE:
print >> buf, template % ("append", "", name, "")
print >> buf, " return tuple(nodes)"
print >> buf, " return tuple(nodelist)"
def _gen_repr(self, buf):
print >> buf, " def __repr__(self):"
@ -208,7 +208,7 @@ def parse_spec(file):
# some extra code for a Node's __init__ method
name = mo.group(1)
cur = classes[name]
return classes.values()
return sorted(classes.values(), key=lambda n: n.name)
def main():
prologue, epilogue = load_boilerplate(sys.argv[-1])
@ -245,9 +245,9 @@ def flatten(list):
def flatten_nodes(list):
return [n for n in flatten(list) if isinstance(n, Node)]
def asList(nodes):
def asList(nodearg):
l = []
for item in nodes:
for item in nodearg:
if hasattr(item, "asList"):
l.append(item.asList())
else:
@ -274,6 +274,21 @@ def getChildNodes(self):
class EmptyNode(Node):
pass
class Expression(Node):
# Expression is an artificial node class to support "eval"
nodes["expression"] = "Expression"
def __init__(self, node):
self.node = node
def getChildren(self):
return self.node,
def getChildNodes(self):
return self.node,
def __repr__(self):
return "Expression(%s)" % (repr(self.node))
### EPILOGUE
klasses = globals()
for k in nodes.keys():

View file

@ -47,6 +47,8 @@ def compile_files(dir):
continue
# make sure the .pyc file is not over-written
os.chmod(source + "c", 444)
elif file == 'CVS':
pass
else:
path = os.path.join(dir, file)
if os.path.isdir(path):