Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0ccff07

Browse files
author
Michael W. Hudson
committed
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)
1 parent b51b234 commit 0ccff07

8 files changed

Lines changed: 144 additions & 98 deletions

File tree

Doc/ref/ref7.tex

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -318,9 +318,9 @@ \section{Function definitions\label{function}}
318318
{[\token{decorators}] "def" \token{funcname} "(" [\token{parameter_list}] ")"
319319
":" \token{suite}}
320320
\production{decorators}
321-
{\token{decorator} ([NEWLINE] \token{decorator})* NEWLINE}
321+
{\token{decorator}+}
322322
\production{decorator}
323-
{"@" \token{dotted_name} ["(" [\token{argument_list} [","]] ")"]}
323+
{"@" \token{dotted_name} ["(" [\token{argument_list} [","]] ")"] NEWLINE}
324324
\production{parameter_list}
325325
{(\token{defparameter} ",")*}
326326
\productioncont{("*" \token{identifier} [, "**" \token{identifier}]}
@@ -352,11 +352,11 @@ \section{Function definitions\label{function}}
352352
that contains the function definition. The result must be a callable,
353353
which is invoked with the function object as the only argument.
354354
The returned value is bound to the function name instead of the function
355-
object. If there are multiple decorators, they are applied in reverse
356-
order. For example, the following code:
355+
object. Multiple decorators are applied in nested fashion.
356+
For example, the following code:
357357

358358
\begin{verbatim}
359-
@f1
359+
@f1(arg)
360360
@f2
361361
def func(): pass
362362
\end{verbatim}
@@ -365,7 +365,7 @@ \section{Function definitions\label{function}}
365365

366366
\begin{verbatim}
367367
def func(): pass
368-
func = f2(f1(func))
368+
func = f1(arg)(f2(func))
369369
\end{verbatim}
370370

371371
When one or more top-level parameters have the form \var{parameter}

Grammar/Grammar

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE
2828
file_input: (NEWLINE | stmt)* ENDMARKER
2929
eval_input: testlist NEWLINE* ENDMARKER
3030

31-
decorator: '@' dotted_name [ '(' [arglist] ')' ]
32-
decorators: decorator ([NEWLINE] decorator)* NEWLINE
31+
decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
32+
decorators: decorator+
3333
funcdef: [decorators] 'def' NAME parameters ':' suite
3434
parameters: '(' [varargslist] ')'
3535
varargslist: (fpdef ['=' test] ',')* ('*' NAME [',' '**' NAME] | '**' NAME) | fpdef ['=' test] (',' fpdef ['=' test])* [',']

Lib/compiler/pycodegen.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,12 +367,12 @@ def visitLambda(self, node):
367367

368368
def _visitFuncOrLambda(self, node, isLambda=0):
369369
if not isLambda and node.decorators:
370-
for decorator in reversed(node.decorators.nodes):
370+
for decorator in node.decorators.nodes:
371371
self.visit(decorator)
372372
ndecorators = len(node.decorators.nodes)
373373
else:
374374
ndecorators = 0
375-
375+
376376
gen = self.FunctionGen(node, self.scopes, isLambda,
377377
self.class_name, self.get_module())
378378
walk(node.code, gen)

Lib/compiler/transformer.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,14 @@ def decorator_name(self, nodelist):
201201

202202
def decorator(self, nodelist):
203203
# '@' dotted_name [ '(' [arglist] ')' ]
204-
assert len(nodelist) in (2, 4, 5)
204+
assert len(nodelist) in (3, 5, 6)
205205
assert nodelist[0][0] == token.AT
206+
assert nodelist[-1][0] == token.NEWLINE
206207

207208
assert nodelist[1][0] == symbol.dotted_name
208209
funcname = self.decorator_name(nodelist[1][1:])
209210

210-
if len(nodelist) > 2:
211+
if len(nodelist) > 3:
211212
assert nodelist[2][0] == token.LPAR
212213
expr = self.com_call_function(funcname, nodelist[3])
213214
else:
@@ -217,16 +218,10 @@ def decorator(self, nodelist):
217218

218219
def decorators(self, nodelist):
219220
# decorators: decorator ([NEWLINE] decorator)* NEWLINE
220-
listlen = len(nodelist)
221-
i = 0
222221
items = []
223-
while i < listlen:
224-
assert nodelist[i][0] == symbol.decorator
225-
items.append(self.decorator(nodelist[i][1:]))
226-
i += 1
227-
228-
if i < listlen and nodelist[i][0] == token.NEWLINE:
229-
i += 1
222+
for dec_nodelist in nodelist:
223+
assert dec_nodelist[0] == symbol.decorator
224+
items.append(self.decorator(dec_nodelist[1:]))
230225
return Decorators(items)
231226

232227
def funcdef(self, nodelist):

Lib/test/test_decorators.py

Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,12 @@ def check(*args, **kwds):
4040
def countcalls(counts):
4141
"Decorator to count calls to a function"
4242
def decorate(func):
43-
name = func.func_name
44-
counts[name] = 0
43+
func_name = func.func_name
44+
counts[func_name] = 0
4545
def call(*args, **kwds):
46-
counts[name] += 1
46+
counts[func_name] += 1
4747
return func(*args, **kwds)
48-
# XXX: Would like to say: call.func_name = func.func_name here
49-
# to make nested decorators work in any order, but func_name
50-
# is a readonly attribute
48+
call.func_name = func_name
5149
return call
5250
return decorate
5351

@@ -65,6 +63,7 @@ def call(*args):
6563
except TypeError:
6664
# Unhashable argument
6765
return func(*args)
66+
call.func_name = func.func_name
6867
return call
6968

7069
# -----------------------------------------------
@@ -126,13 +125,13 @@ def f(a, b):
126125
self.assertRaises(DbcheckError, f, 1, None)
127126

128127
def test_memoize(self):
129-
# XXX: This doesn't work unless memoize is the last decorator -
130-
# see the comment in countcalls.
131128
counts = {}
129+
132130
@memoize
133131
@countcalls(counts)
134132
def double(x):
135133
return x * 2
134+
self.assertEqual(double.func_name, 'double')
136135

137136
self.assertEqual(counts, dict(double=0))
138137

@@ -162,6 +161,11 @@ def test_errors(self):
162161
codestr = "@%s\ndef f(): pass" % expr
163162
self.assertRaises(SyntaxError, compile, codestr, "test", "exec")
164163

164+
# You can't put multiple decorators on a single line:
165+
#
166+
self.assertRaises(SyntaxError, compile,
167+
"@f1 @f2\ndef f(): pass", "test", "exec")
168+
165169
# Test runtime errors
166170

167171
def unimp(func):
@@ -187,20 +191,74 @@ def foo(self): return 42
187191
self.assertEqual(C.foo.booh, 42)
188192

189193
def test_order(self):
190-
# Test that decorators are conceptually applied right-recursively;
191-
# that means bottom-up
192-
def ordercheck(num):
193-
def deco(func):
194-
return lambda: num
195-
return deco
196-
197-
# Should go ordercheck(1)(ordercheck(2)(blah)) which should lead to
198-
# blah() == 1
199-
@ordercheck(1)
200-
@ordercheck(2)
201-
def blah(): pass
202-
self.assertEqual(blah(), 1, "decorators are meant to be applied "
203-
"bottom-up")
194+
class C(object):
195+
@staticmethod
196+
@funcattrs(abc=1)
197+
def foo(): return 42
198+
# This wouldn't work if staticmethod was called first
199+
self.assertEqual(C.foo(), 42)
200+
self.assertEqual(C().foo(), 42)
201+
202+
def test_eval_order(self):
203+
# Evaluating a decorated function involves four steps for each
204+
# decorator-maker (the function that returns a decorator):
205+
#
206+
# 1: Evaluate the decorator-maker name
207+
# 2: Evaluate the decorator-maker arguments (if any)
208+
# 3: Call the decorator-maker to make a decorator
209+
# 4: Call the decorator
210+
#
211+
# When there are multiple decorators, these steps should be
212+
# performed in the above order for each decorator, but we should
213+
# iterate through the decorators in the reverse of the order they
214+
# appear in the source.
215+
216+
actions = []
217+
218+
def make_decorator(tag):
219+
actions.append('makedec' + tag)
220+
def decorate(func):
221+
actions.append('calldec' + tag)
222+
return func
223+
return decorate
224+
225+
class NameLookupTracer (object):
226+
def __init__(self, index):
227+
self.index = index
228+
229+
def __getattr__(self, fname):
230+
if fname == 'make_decorator':
231+
opname, res = ('evalname', make_decorator)
232+
elif fname == 'arg':
233+
opname, res = ('evalargs', str(self.index))
234+
else:
235+
assert False, "Unknown attrname %s" % fname
236+
actions.append('%s%d' % (opname, self.index))
237+
return res
238+
239+
c1, c2, c3 = map(NameLookupTracer, [ 1, 2, 3 ])
240+
241+
expected_actions = [ 'evalname1', 'evalargs1', 'makedec1',
242+
'evalname2', 'evalargs2', 'makedec2',
243+
'evalname3', 'evalargs3', 'makedec3',
244+
'calldec3', 'calldec2', 'calldec1' ]
245+
246+
actions = []
247+
@c1.make_decorator(c1.arg)
248+
@c2.make_decorator(c2.arg)
249+
@c3.make_decorator(c3.arg)
250+
def foo(): return 42
251+
self.assertEqual(foo(), 42)
252+
253+
self.assertEqual(actions, expected_actions)
254+
255+
# Test the equivalence claim in chapter 7 of the reference manual.
256+
#
257+
actions = []
258+
def bar(): return 42
259+
bar = c1.make_decorator(c1.arg)(c2.make_decorator(c2.arg)(c3.make_decorator(c3.arg)(bar)))
260+
self.assertEqual(bar(), 42)
261+
self.assertEqual(actions, expected_actions)
204262

205263
def test_main():
206264
test_support.run_unittest(TestDecorators)

Modules/parsermodule.c

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2364,49 +2364,45 @@ validate_testlist_gexp(node *tree)
23642364
}
23652365

23662366
/* decorator:
2367-
* '@' dotted_name [ '(' [arglist] ')' ]
2367+
* '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
23682368
*/
23692369
static int
23702370
validate_decorator(node *tree)
23712371
{
23722372
int ok;
23732373
int nch = NCH(tree);
23742374
ok = (validate_ntype(tree, decorator) &&
2375-
(nch == 2 || nch == 4 || nch == 5) &&
2375+
(nch == 3 || nch == 5 || nch == 6) &&
23762376
validate_at(CHILD(tree, 0)) &&
2377-
validate_dotted_name(CHILD(tree, 1)));
2377+
validate_dotted_name(CHILD(tree, 1)) &&
2378+
validate_newline(RCHILD(tree, -1)));
23782379

2379-
if (ok && nch != 2) {
2380-
ok = (validate_lparen(CHILD(tree, 2)) &&
2381-
validate_rparen(RCHILD(tree, -1)));
2380+
if (ok && nch != 3) {
2381+
ok = (validate_lparen(CHILD(tree, 2)) &&
2382+
validate_rparen(RCHILD(tree, -2)));
23822383

2383-
if (ok && nch == 5)
2384-
ok = validate_arglist(CHILD(tree, 3));
2384+
if (ok && nch == 6)
2385+
ok = validate_arglist(CHILD(tree, 3));
23852386
}
23862387

23872388
return ok;
23882389
}
2389-
2390+
23902391
/* decorators:
2391-
* decorator ([NEWLINE] decorator)* NEWLINE
2392+
* decorator+
23922393
*/
23932394
static int
23942395
validate_decorators(node *tree)
23952396
{
23962397
int i, nch, ok;
23972398
nch = NCH(tree);
2398-
ok = validate_ntype(tree, decorators) && nch >= 2;
2399+
ok = validate_ntype(tree, decorators) && nch >= 1;
23992400

2400-
i = 0;
2401-
while (ok && i < nch - 1) {
2401+
for (i = 0; ok && i < nch; ++i)
24022402
ok = validate_decorator(CHILD(tree, i));
2403-
if (TYPE(CHILD(tree, i + 1)) == NEWLINE)
2404-
++i;
2405-
++i;
2406-
}
24072403

24082404
return ok;
2409-
}
2405+
}
24102406

24112407
/* funcdef:
24122408
*

Python/compile.c

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4107,50 +4107,46 @@ com_decorator_name(struct compiling *c, node *n)
41074107
static void
41084108
com_decorator(struct compiling *c, node *n)
41094109
{
4110-
/* decorator: '@' dotted_name [ '(' [arglist] ')' ] */
4110+
/* decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE */
41114111
int nch = NCH(n);
4112-
assert(nch >= 2);
4112+
assert(nch >= 3);
41134113
REQ(CHILD(n, 0), AT);
4114+
REQ(RCHILD(n, -1), NEWLINE);
41144115
com_decorator_name(c, CHILD(n, 1));
41154116

4116-
if (nch > 2) {
4117-
assert(nch == 4 || nch == 5);
4117+
if (nch > 3) {
4118+
assert(nch == 5 || nch == 6);
41184119
REQ(CHILD(n, 2), LPAR);
4119-
REQ(CHILD(n, nch - 1), RPAR);
4120+
REQ(RCHILD(n, -2), RPAR);
41204121
com_call_function(c, CHILD(n, 3));
41214122
}
41224123
}
41234124

41244125
static int
41254126
com_decorators(struct compiling *c, node *n)
41264127
{
4127-
int i, nch, ndecorators;
4128+
int i, nch;
41284129

4129-
/* decorator ([NEWLINE] decorator)* NEWLINE */
4130+
/* decorator+ */
41304131
nch = NCH(n);
4131-
assert(nch >= 2);
4132-
REQ(CHILD(n, nch - 1), NEWLINE);
4133-
4134-
ndecorators = 0;
4135-
/* the application order for decorators is the reverse of how they are
4136-
listed; bottom-up */
4137-
nch -= 1;
4138-
for (i = 0; i < nch; i+=1) {
4132+
assert(nch >= 1);
4133+
4134+
for (i = 0; i < nch; ++i) {
41394135
node *ch = CHILD(n, i);
4140-
if (TYPE(ch) != NEWLINE) {
4141-
com_decorator(c, ch);
4142-
++ndecorators;
4143-
}
4136+
REQ(ch, decorator);
4137+
4138+
com_decorator(c, ch);
41444139
}
41454140

4146-
return ndecorators;
4141+
return nch;
41474142
}
41484143

41494144
static void
41504145
com_funcdef(struct compiling *c, node *n)
41514146
{
41524147
PyObject *co;
41534148
int ndefs, ndecorators;
4149+
41544150
REQ(n, funcdef);
41554151
/* -6 -5 -4 -3 -2 -1
41564152
funcdef: [decorators] 'def' NAME parameters ':' suite */
@@ -4159,7 +4155,7 @@ com_funcdef(struct compiling *c, node *n)
41594155
ndecorators = com_decorators(c, CHILD(n, 0));
41604156
else
41614157
ndecorators = 0;
4162-
4158+
41634159
ndefs = com_argdefs(c, n);
41644160
if (ndefs < 0)
41654161
return;
@@ -4179,11 +4175,13 @@ com_funcdef(struct compiling *c, node *n)
41794175
else
41804176
com_addoparg(c, MAKE_FUNCTION, ndefs);
41814177
com_pop(c, ndefs);
4178+
41824179
while (ndecorators > 0) {
41834180
com_addoparg(c, CALL_FUNCTION, 1);
41844181
com_pop(c, 1);
4185-
ndecorators--;
4182+
--ndecorators;
41864183
}
4184+
41874185
com_addop_varname(c, VAR_STORE, STR(RCHILD(n, -4)));
41884186
com_pop(c, 1);
41894187
Py_DECREF(co);

0 commit comments

Comments
 (0)