mirror of
https://github.com/python/cpython.git
synced 2025-08-03 16:39:00 +00:00
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:
parent
b51b23405b
commit
0ccff074cd
8 changed files with 142 additions and 96 deletions
|
@ -367,12 +367,12 @@ class CodeGenerator:
|
|||
|
||||
def _visitFuncOrLambda(self, node, isLambda=0):
|
||||
if not isLambda and node.decorators:
|
||||
for decorator in reversed(node.decorators.nodes):
|
||||
for decorator in 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)
|
||||
|
|
|
@ -201,13 +201,14 @@ class Transformer:
|
|||
|
||||
def decorator(self, nodelist):
|
||||
# '@' 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[-1][0] == token.NEWLINE
|
||||
|
||||
assert nodelist[1][0] == symbol.dotted_name
|
||||
funcname = self.decorator_name(nodelist[1][1:])
|
||||
|
||||
if len(nodelist) > 2:
|
||||
if len(nodelist) > 3:
|
||||
assert nodelist[2][0] == token.LPAR
|
||||
expr = self.com_call_function(funcname, nodelist[3])
|
||||
else:
|
||||
|
@ -217,16 +218,10 @@ class Transformer:
|
|||
|
||||
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
|
||||
for dec_nodelist in nodelist:
|
||||
assert dec_nodelist[0] == symbol.decorator
|
||||
items.append(self.decorator(dec_nodelist[1:]))
|
||||
return Decorators(items)
|
||||
|
||||
def funcdef(self, nodelist):
|
||||
|
|
|
@ -40,14 +40,12 @@ def dbcheck(exprstr, globals=None, locals=None):
|
|||
def countcalls(counts):
|
||||
"Decorator to count calls to a function"
|
||||
def decorate(func):
|
||||
name = func.func_name
|
||||
counts[name] = 0
|
||||
func_name = func.func_name
|
||||
counts[func_name] = 0
|
||||
def call(*args, **kwds):
|
||||
counts[name] += 1
|
||||
counts[func_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
|
||||
call.func_name = func_name
|
||||
return call
|
||||
return decorate
|
||||
|
||||
|
@ -65,6 +63,7 @@ def memoize(func):
|
|||
except TypeError:
|
||||
# Unhashable argument
|
||||
return func(*args)
|
||||
call.func_name = func.func_name
|
||||
return call
|
||||
|
||||
# -----------------------------------------------
|
||||
|
@ -126,13 +125,13 @@ class TestDecorators(unittest.TestCase):
|
|||
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 = {}
|
||||
|
||||
@memoize
|
||||
@countcalls(counts)
|
||||
def double(x):
|
||||
return x * 2
|
||||
self.assertEqual(double.func_name, 'double')
|
||||
|
||||
self.assertEqual(counts, dict(double=0))
|
||||
|
||||
|
@ -162,6 +161,11 @@ class TestDecorators(unittest.TestCase):
|
|||
codestr = "@%s\ndef f(): pass" % expr
|
||||
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
|
||||
|
||||
def unimp(func):
|
||||
|
@ -187,20 +191,74 @@ class TestDecorators(unittest.TestCase):
|
|||
self.assertEqual(C.foo.booh, 42)
|
||||
|
||||
def test_order(self):
|
||||
# Test that decorators are conceptually applied right-recursively;
|
||||
# that means bottom-up
|
||||
def ordercheck(num):
|
||||
def deco(func):
|
||||
return lambda: num
|
||||
return deco
|
||||
class C(object):
|
||||
@staticmethod
|
||||
@funcattrs(abc=1)
|
||||
def foo(): return 42
|
||||
# This wouldn't work if staticmethod was called first
|
||||
self.assertEqual(C.foo(), 42)
|
||||
self.assertEqual(C().foo(), 42)
|
||||
|
||||
# Should go ordercheck(1)(ordercheck(2)(blah)) which should lead to
|
||||
# blah() == 1
|
||||
@ordercheck(1)
|
||||
@ordercheck(2)
|
||||
def blah(): pass
|
||||
self.assertEqual(blah(), 1, "decorators are meant to be applied "
|
||||
"bottom-up")
|
||||
def test_eval_order(self):
|
||||
# Evaluating a decorated function involves four steps for each
|
||||
# decorator-maker (the function that returns a decorator):
|
||||
#
|
||||
# 1: Evaluate the decorator-maker name
|
||||
# 2: Evaluate the decorator-maker arguments (if any)
|
||||
# 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():
|
||||
test_support.run_unittest(TestDecorators)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue