@@ -40,14 +40,12 @@ def check(*args, **kwds):
4040def 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\n def 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\n def 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
205263def test_main ():
206264 test_support .run_unittest (TestDecorators )
0 commit comments