This is Mark Russell's patch:

[ 1009560 ] Fix @decorator evaluation order

From the description:

Changes in this patch:

- Change Grammar/Grammar to require
newlines between adjacent decorators.

- Fix order of evaluation of decorators
in the C (compile.c) and python
(Lib/compiler/pycodegen.py) compilers

- Add better order of evaluation check
to test_decorators.py (test_eval_order)

- Update the decorator documentation in
the reference manual (improve description
of evaluation order and update syntax
description)

and the comment:

Used Brett's evaluation order (see
http://mail.python.org/pipermail/python-dev/2004-August/047835.html)

(I'm checking this in for Anthony who was having problems getting SF to
talk to him)
This commit is contained in:
Michael W. Hudson 2004-08-17 17:29:16 +00:00
parent b51b23405b
commit 0ccff074cd
8 changed files with 142 additions and 96 deletions

View File

@ -318,9 +318,9 @@ section~\ref{types}):
{[\token{decorators}] "def" \token{funcname} "(" [\token{parameter_list}] ")" {[\token{decorators}] "def" \token{funcname} "(" [\token{parameter_list}] ")"
":" \token{suite}} ":" \token{suite}}
\production{decorators} \production{decorators}
{\token{decorator} ([NEWLINE] \token{decorator})* NEWLINE} {\token{decorator}+}
\production{decorator} \production{decorator}
{"@" \token{dotted_name} ["(" [\token{argument_list} [","]] ")"]} {"@" \token{dotted_name} ["(" [\token{argument_list} [","]] ")"] NEWLINE}
\production{parameter_list} \production{parameter_list}
{(\token{defparameter} ",")*} {(\token{defparameter} ",")*}
\productioncont{("*" \token{identifier} [, "**" \token{identifier}]} \productioncont{("*" \token{identifier} [, "**" \token{identifier}]}
@ -352,11 +352,11 @@ Decorator expressions are evaluated when the function is defined, in the scope
that contains the function definition. The result must be a callable, that contains the function definition. The result must be a callable,
which is invoked with the function object as the only argument. which is invoked with the function object as the only argument.
The returned value is bound to the function name instead of the function The returned value is bound to the function name instead of the function
object. If there are multiple decorators, they are applied in reverse object. Multiple decorators are applied in nested fashion.
order. For example, the following code: For example, the following code:
\begin{verbatim} \begin{verbatim}
@f1 @f1(arg)
@f2 @f2
def func(): pass def func(): pass
\end{verbatim} \end{verbatim}
@ -365,7 +365,7 @@ is equivalent to:
\begin{verbatim} \begin{verbatim}
def func(): pass def func(): pass
func = f2(f1(func)) func = f1(arg)(f2(func))
\end{verbatim} \end{verbatim}
When one or more top-level parameters have the form \var{parameter} When one or more top-level parameters have the form \var{parameter}

View File

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

View File

@ -367,12 +367,12 @@ class CodeGenerator:
def _visitFuncOrLambda(self, node, isLambda=0): def _visitFuncOrLambda(self, node, isLambda=0):
if not isLambda and node.decorators: if not isLambda and node.decorators:
for decorator in reversed(node.decorators.nodes): for decorator in node.decorators.nodes:
self.visit(decorator) self.visit(decorator)
ndecorators = len(node.decorators.nodes) ndecorators = len(node.decorators.nodes)
else: else:
ndecorators = 0 ndecorators = 0
gen = self.FunctionGen(node, self.scopes, isLambda, gen = self.FunctionGen(node, self.scopes, isLambda,
self.class_name, self.get_module()) self.class_name, self.get_module())
walk(node.code, gen) walk(node.code, gen)

View File

@ -201,13 +201,14 @@ class Transformer:
def decorator(self, nodelist): def decorator(self, nodelist):
# '@' dotted_name [ '(' [arglist] ')' ] # '@' dotted_name [ '(' [arglist] ')' ]
assert len(nodelist) in (2, 4, 5) assert len(nodelist) in (3, 5, 6)
assert nodelist[0][0] == token.AT assert nodelist[0][0] == token.AT
assert nodelist[-1][0] == token.NEWLINE
assert nodelist[1][0] == symbol.dotted_name assert nodelist[1][0] == symbol.dotted_name
funcname = self.decorator_name(nodelist[1][1:]) funcname = self.decorator_name(nodelist[1][1:])
if len(nodelist) > 2: if len(nodelist) > 3:
assert nodelist[2][0] == token.LPAR assert nodelist[2][0] == token.LPAR
expr = self.com_call_function(funcname, nodelist[3]) expr = self.com_call_function(funcname, nodelist[3])
else: else:
@ -217,16 +218,10 @@ class Transformer:
def decorators(self, nodelist): def decorators(self, nodelist):
# decorators: decorator ([NEWLINE] decorator)* NEWLINE # decorators: decorator ([NEWLINE] decorator)* NEWLINE
listlen = len(nodelist)
i = 0
items = [] items = []
while i < listlen: for dec_nodelist in nodelist:
assert nodelist[i][0] == symbol.decorator assert dec_nodelist[0] == symbol.decorator
items.append(self.decorator(nodelist[i][1:])) items.append(self.decorator(dec_nodelist[1:]))
i += 1
if i < listlen and nodelist[i][0] == token.NEWLINE:
i += 1
return Decorators(items) return Decorators(items)
def funcdef(self, nodelist): def funcdef(self, nodelist):

View File

@ -40,14 +40,12 @@ def dbcheck(exprstr, globals=None, locals=None):
def countcalls(counts): def countcalls(counts):
"Decorator to count calls to a function" "Decorator to count calls to a function"
def decorate(func): def decorate(func):
name = func.func_name func_name = func.func_name
counts[name] = 0 counts[func_name] = 0
def call(*args, **kwds): def call(*args, **kwds):
counts[name] += 1 counts[func_name] += 1
return func(*args, **kwds) return func(*args, **kwds)
# XXX: Would like to say: call.func_name = func.func_name here call.func_name = func_name
# to make nested decorators work in any order, but func_name
# is a readonly attribute
return call return call
return decorate return decorate
@ -65,6 +63,7 @@ def memoize(func):
except TypeError: except TypeError:
# Unhashable argument # Unhashable argument
return func(*args) return func(*args)
call.func_name = func.func_name
return call return call
# ----------------------------------------------- # -----------------------------------------------
@ -126,13 +125,13 @@ class TestDecorators(unittest.TestCase):
self.assertRaises(DbcheckError, f, 1, None) self.assertRaises(DbcheckError, f, 1, None)
def test_memoize(self): def test_memoize(self):
# XXX: This doesn't work unless memoize is the last decorator -
# see the comment in countcalls.
counts = {} counts = {}
@memoize @memoize
@countcalls(counts) @countcalls(counts)
def double(x): def double(x):
return x * 2 return x * 2
self.assertEqual(double.func_name, 'double')
self.assertEqual(counts, dict(double=0)) self.assertEqual(counts, dict(double=0))
@ -162,6 +161,11 @@ class TestDecorators(unittest.TestCase):
codestr = "@%s\ndef f(): pass" % expr codestr = "@%s\ndef f(): pass" % expr
self.assertRaises(SyntaxError, compile, codestr, "test", "exec") self.assertRaises(SyntaxError, compile, codestr, "test", "exec")
# You can't put multiple decorators on a single line:
#
self.assertRaises(SyntaxError, compile,
"@f1 @f2\ndef f(): pass", "test", "exec")
# Test runtime errors # Test runtime errors
def unimp(func): def unimp(func):
@ -187,20 +191,74 @@ class TestDecorators(unittest.TestCase):
self.assertEqual(C.foo.booh, 42) self.assertEqual(C.foo.booh, 42)
def test_order(self): def test_order(self):
# Test that decorators are conceptually applied right-recursively; class C(object):
# that means bottom-up @staticmethod
def ordercheck(num): @funcattrs(abc=1)
def deco(func): def foo(): return 42
return lambda: num # This wouldn't work if staticmethod was called first
return deco self.assertEqual(C.foo(), 42)
self.assertEqual(C().foo(), 42)
# Should go ordercheck(1)(ordercheck(2)(blah)) which should lead to def test_eval_order(self):
# blah() == 1 # Evaluating a decorated function involves four steps for each
@ordercheck(1) # decorator-maker (the function that returns a decorator):
@ordercheck(2) #
def blah(): pass # 1: Evaluate the decorator-maker name
self.assertEqual(blah(), 1, "decorators are meant to be applied " # 2: Evaluate the decorator-maker arguments (if any)
"bottom-up") # 3: Call the decorator-maker to make a decorator
# 4: Call the decorator
#
# When there are multiple decorators, these steps should be
# performed in the above order for each decorator, but we should
# iterate through the decorators in the reverse of the order they
# appear in the source.
actions = []
def make_decorator(tag):
actions.append('makedec' + tag)
def decorate(func):
actions.append('calldec' + tag)
return func
return decorate
class NameLookupTracer (object):
def __init__(self, index):
self.index = index
def __getattr__(self, fname):
if fname == 'make_decorator':
opname, res = ('evalname', make_decorator)
elif fname == 'arg':
opname, res = ('evalargs', str(self.index))
else:
assert False, "Unknown attrname %s" % fname
actions.append('%s%d' % (opname, self.index))
return res
c1, c2, c3 = map(NameLookupTracer, [ 1, 2, 3 ])
expected_actions = [ 'evalname1', 'evalargs1', 'makedec1',
'evalname2', 'evalargs2', 'makedec2',
'evalname3', 'evalargs3', 'makedec3',
'calldec3', 'calldec2', 'calldec1' ]
actions = []
@c1.make_decorator(c1.arg)
@c2.make_decorator(c2.arg)
@c3.make_decorator(c3.arg)
def foo(): return 42
self.assertEqual(foo(), 42)
self.assertEqual(actions, expected_actions)
# Test the equivalence claim in chapter 7 of the reference manual.
#
actions = []
def bar(): return 42
bar = c1.make_decorator(c1.arg)(c2.make_decorator(c2.arg)(c3.make_decorator(c3.arg)(bar)))
self.assertEqual(bar(), 42)
self.assertEqual(actions, expected_actions)
def test_main(): def test_main():
test_support.run_unittest(TestDecorators) test_support.run_unittest(TestDecorators)

View File

@ -2364,7 +2364,7 @@ validate_testlist_gexp(node *tree)
} }
/* decorator: /* decorator:
* '@' dotted_name [ '(' [arglist] ')' ] * '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
*/ */
static int static int
validate_decorator(node *tree) validate_decorator(node *tree)
@ -2372,41 +2372,37 @@ validate_decorator(node *tree)
int ok; int ok;
int nch = NCH(tree); int nch = NCH(tree);
ok = (validate_ntype(tree, decorator) && ok = (validate_ntype(tree, decorator) &&
(nch == 2 || nch == 4 || nch == 5) && (nch == 3 || nch == 5 || nch == 6) &&
validate_at(CHILD(tree, 0)) && validate_at(CHILD(tree, 0)) &&
validate_dotted_name(CHILD(tree, 1))); validate_dotted_name(CHILD(tree, 1)) &&
validate_newline(RCHILD(tree, -1)));
if (ok && nch != 2) { if (ok && nch != 3) {
ok = (validate_lparen(CHILD(tree, 2)) && ok = (validate_lparen(CHILD(tree, 2)) &&
validate_rparen(RCHILD(tree, -1))); validate_rparen(RCHILD(tree, -2)));
if (ok && nch == 5) if (ok && nch == 6)
ok = validate_arglist(CHILD(tree, 3)); ok = validate_arglist(CHILD(tree, 3));
} }
return ok; return ok;
} }
/* decorators: /* decorators:
* decorator ([NEWLINE] decorator)* NEWLINE * decorator+
*/ */
static int static int
validate_decorators(node *tree) validate_decorators(node *tree)
{ {
int i, nch, ok; int i, nch, ok;
nch = NCH(tree); nch = NCH(tree);
ok = validate_ntype(tree, decorators) && nch >= 2; ok = validate_ntype(tree, decorators) && nch >= 1;
i = 0; for (i = 0; ok && i < nch; ++i)
while (ok && i < nch - 1) {
ok = validate_decorator(CHILD(tree, i)); ok = validate_decorator(CHILD(tree, i));
if (TYPE(CHILD(tree, i + 1)) == NEWLINE)
++i;
++i;
}
return ok; return ok;
} }
/* funcdef: /* funcdef:
* *

View File

@ -4107,16 +4107,17 @@ com_decorator_name(struct compiling *c, node *n)
static void static void
com_decorator(struct compiling *c, node *n) com_decorator(struct compiling *c, node *n)
{ {
/* decorator: '@' dotted_name [ '(' [arglist] ')' ] */ /* decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE */
int nch = NCH(n); int nch = NCH(n);
assert(nch >= 2); assert(nch >= 3);
REQ(CHILD(n, 0), AT); REQ(CHILD(n, 0), AT);
REQ(RCHILD(n, -1), NEWLINE);
com_decorator_name(c, CHILD(n, 1)); com_decorator_name(c, CHILD(n, 1));
if (nch > 2) { if (nch > 3) {
assert(nch == 4 || nch == 5); assert(nch == 5 || nch == 6);
REQ(CHILD(n, 2), LPAR); REQ(CHILD(n, 2), LPAR);
REQ(CHILD(n, nch - 1), RPAR); REQ(RCHILD(n, -2), RPAR);
com_call_function(c, CHILD(n, 3)); com_call_function(c, CHILD(n, 3));
} }
} }
@ -4124,26 +4125,20 @@ com_decorator(struct compiling *c, node *n)
static int static int
com_decorators(struct compiling *c, node *n) com_decorators(struct compiling *c, node *n)
{ {
int i, nch, ndecorators; int i, nch;
/* decorator ([NEWLINE] decorator)* NEWLINE */ /* decorator+ */
nch = NCH(n); nch = NCH(n);
assert(nch >= 2); assert(nch >= 1);
REQ(CHILD(n, nch - 1), NEWLINE);
ndecorators = 0; for (i = 0; i < nch; ++i) {
/* the application order for decorators is the reverse of how they are
listed; bottom-up */
nch -= 1;
for (i = 0; i < nch; i+=1) {
node *ch = CHILD(n, i); node *ch = CHILD(n, i);
if (TYPE(ch) != NEWLINE) { REQ(ch, decorator);
com_decorator(c, ch);
++ndecorators; com_decorator(c, ch);
}
} }
return ndecorators; return nch;
} }
static void static void
@ -4151,6 +4146,7 @@ com_funcdef(struct compiling *c, node *n)
{ {
PyObject *co; PyObject *co;
int ndefs, ndecorators; int ndefs, ndecorators;
REQ(n, funcdef); REQ(n, funcdef);
/* -6 -5 -4 -3 -2 -1 /* -6 -5 -4 -3 -2 -1
funcdef: [decorators] 'def' NAME parameters ':' suite */ funcdef: [decorators] 'def' NAME parameters ':' suite */
@ -4159,7 +4155,7 @@ com_funcdef(struct compiling *c, node *n)
ndecorators = com_decorators(c, CHILD(n, 0)); ndecorators = com_decorators(c, CHILD(n, 0));
else else
ndecorators = 0; ndecorators = 0;
ndefs = com_argdefs(c, n); ndefs = com_argdefs(c, n);
if (ndefs < 0) if (ndefs < 0)
return; return;
@ -4179,11 +4175,13 @@ com_funcdef(struct compiling *c, node *n)
else else
com_addoparg(c, MAKE_FUNCTION, ndefs); com_addoparg(c, MAKE_FUNCTION, ndefs);
com_pop(c, ndefs); com_pop(c, ndefs);
while (ndecorators > 0) { while (ndecorators > 0) {
com_addoparg(c, CALL_FUNCTION, 1); com_addoparg(c, CALL_FUNCTION, 1);
com_pop(c, 1); com_pop(c, 1);
ndecorators--; --ndecorators;
} }
com_addop_varname(c, VAR_STORE, STR(RCHILD(n, -4))); com_addop_varname(c, VAR_STORE, STR(RCHILD(n, -4)));
com_pop(c, 1); com_pop(c, 1);
Py_DECREF(co); Py_DECREF(co);

View File

@ -51,41 +51,40 @@ static arc arcs_3_1[1] = {
}; };
static arc arcs_3_2[2] = { static arc arcs_3_2[2] = {
{13, 3}, {13, 3},
{0, 2}, {2, 4},
}; };
static arc arcs_3_3[2] = { static arc arcs_3_3[2] = {
{14, 4}, {14, 5},
{15, 5}, {15, 6},
}; };
static arc arcs_3_4[1] = { static arc arcs_3_4[1] = {
{15, 5}, {0, 4},
}; };
static arc arcs_3_5[1] = { static arc arcs_3_5[1] = {
{0, 5}, {15, 6},
}; };
static state states_3[6] = { static arc arcs_3_6[1] = {
{2, 4},
};
static state states_3[7] = {
{1, arcs_3_0}, {1, arcs_3_0},
{1, arcs_3_1}, {1, arcs_3_1},
{2, arcs_3_2}, {2, arcs_3_2},
{2, arcs_3_3}, {2, arcs_3_3},
{1, arcs_3_4}, {1, arcs_3_4},
{1, arcs_3_5}, {1, arcs_3_5},
{1, arcs_3_6},
}; };
static arc arcs_4_0[1] = { static arc arcs_4_0[1] = {
{10, 1}, {10, 1},
}; };
static arc arcs_4_1[2] = { static arc arcs_4_1[2] = {
{2, 2},
{10, 1}, {10, 1},
{0, 1},
}; };
static arc arcs_4_2[2] = { static state states_4[2] = {
{10, 1},
{0, 2},
};
static state states_4[3] = {
{1, arcs_4_0}, {1, arcs_4_0},
{2, arcs_4_1}, {2, arcs_4_1},
{2, arcs_4_2},
}; };
static arc arcs_5_0[2] = { static arc arcs_5_0[2] = {
{16, 1}, {16, 1},
@ -1618,9 +1617,9 @@ static dfa dfas[74] = {
"\204\050\014\000\000\000\200\012\176\231\040\007\040\000\000\206\220\064\041\000"}, "\204\050\014\000\000\000\200\012\176\231\040\007\040\000\000\206\220\064\041\000"},
{258, "eval_input", 0, 3, states_2, {258, "eval_input", 0, 3, states_2,
"\000\040\010\000\000\000\000\000\000\000\000\000\040\000\000\206\220\064\001\000"}, "\000\040\010\000\000\000\000\000\000\000\000\000\040\000\000\206\220\064\001\000"},
{259, "decorator", 0, 6, states_3, {259, "decorator", 0, 7, states_3,
"\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, "\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"},
{260, "decorators", 0, 3, states_4, {260, "decorators", 0, 2, states_4,
"\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, "\000\010\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"},
{261, "funcdef", 0, 7, states_5, {261, "funcdef", 0, 7, states_5,
"\000\010\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"}, "\000\010\004\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000"},