0001 import unittest 0002 from test import test_support 0003 0004 def funcattrs(**kwds): 0005 def decorate(func): 0006 func.__dict__.update(kwds) 0007 return func 0008 return decorate 0009 0010 class MiscDecorators (object): 0011 @staticmethod 0012 def author(name): 0013 def decorate(func): 0014 func.__dict__['author'] = name 0015 return func 0016 return decorate 0017 0018 # ----------------------------------------------- 0019 0020 class DbcheckError (Exception): 0021 def __init__(self, exprstr, func, args, kwds): 0022 # A real version of this would set attributes here 0023 Exception.__init__(self, "dbcheck %r failed (func=%s args=%s kwds=%s)" % 0024 (exprstr, func, args, kwds)) 0025 0026 0027 def dbcheck(exprstr, globals=None, locals=None): 0028 "Decorator to implement debugging assertions" 0029 def decorate(func): 0030 expr = compile(exprstr, "dbcheck-%s" % func.func_name, "eval") 0031 def check(*args, **kwds): 0032 if not eval(expr, globals, locals): 0033 raise DbcheckError(exprstr, func, args, kwds) 0034 return func(*args, **kwds) 0035 return check 0036 return decorate 0037 0038 # ----------------------------------------------- 0039 0040 def countcalls(counts): 0041 "Decorator to count calls to a function" 0042 def decorate(func): 0043 func_name = func.func_name 0044 counts[func_name] = 0 0045 def call(*args, **kwds): 0046 counts[func_name] += 1 0047 return func(*args, **kwds) 0048 call.func_name = func_name 0049 return call 0050 return decorate 0051 0052 # ----------------------------------------------- 0053 0054 def memoize(func): 0055 saved = {} 0056 def call(*args): 0057 try: 0058 return saved[args] 0059 except KeyError: 0060 res = func(*args) 0061 saved[args] = res 0062 return res 0063 except TypeError: 0064 # Unhashable argument 0065 return func(*args) 0066 call.func_name = func.func_name 0067 return call 0068 0069 # ----------------------------------------------- 0070 0071 class TestDecorators(unittest.TestCase): 0072 0073 def test_single(self): 0074 class C(object): 0075 @staticmethod 0076 def foo(): return 42 0077 self.assertEqual(C.foo(), 42) 0078 self.assertEqual(C().foo(), 42) 0079 0080 def test_staticmethod_function(self): 0081 @staticmethod 0082 def notamethod(x): 0083 return x 0084 self.assertRaises(TypeError, notamethod, 1) 0085 0086 def test_dotted(self): 0087 decorators = MiscDecorators() 0088 @decorators.author('Cleese') 0089 def foo(): return 42 0090 self.assertEqual(foo(), 42) 0091 self.assertEqual(foo.author, 'Cleese') 0092 0093 def test_argforms(self): 0094 # A few tests of argument passing, as we use restricted form 0095 # of expressions for decorators. 0096 0097 def noteargs(*args, **kwds): 0098 def decorate(func): 0099 setattr(func, 'dbval', (args, kwds)) 0100 return func 0101 return decorate 0102 0103 args = ( 'Now', 'is', 'the', 'time' ) 0104 kwds = dict(one=1, two=2) 0105 @noteargs(*args, **kwds) 0106 def f1(): return 42 0107 self.assertEqual(f1(), 42) 0108 self.assertEqual(f1.dbval, (args, kwds)) 0109 0110 @noteargs('terry', 'gilliam', eric='idle', john='cleese') 0111 def f2(): return 84 0112 self.assertEqual(f2(), 84) 0113 self.assertEqual(f2.dbval, (('terry', 'gilliam'), 0114 dict(eric='idle', john='cleese'))) 0115 0116 @noteargs(1, 2,) 0117 def f3(): pass 0118 self.assertEqual(f3.dbval, ((1, 2), {})) 0119 0120 def test_dbcheck(self): 0121 @dbcheck('args[1] is not None') 0122 def f(a, b): 0123 return a + b 0124 self.assertEqual(f(1, 2), 3) 0125 self.assertRaises(DbcheckError, f, 1, None) 0126 0127 def test_memoize(self): 0128 counts = {} 0129 0130 @memoize 0131 @countcalls(counts) 0132 def double(x): 0133 return x * 2 0134 self.assertEqual(double.func_name, 'double') 0135 0136 self.assertEqual(counts, dict(double=0)) 0137 0138 # Only the first call with a given argument bumps the call count: 0139 # 0140 self.assertEqual(double(2), 4) 0141 self.assertEqual(counts['double'], 1) 0142 self.assertEqual(double(2), 4) 0143 self.assertEqual(counts['double'], 1) 0144 self.assertEqual(double(3), 6) 0145 self.assertEqual(counts['double'], 2) 0146 0147 # Unhashable arguments do not get memoized: 0148 # 0149 self.assertEqual(double([10]), [10, 10]) 0150 self.assertEqual(counts['double'], 3) 0151 self.assertEqual(double([10]), [10, 10]) 0152 self.assertEqual(counts['double'], 4) 0153 0154 def test_errors(self): 0155 # Test syntax restrictions - these are all compile-time errors: 0156 # 0157 for expr in [ "1+2", "x[3]", "(1, 2)" ]: 0158 # Sanity check: is expr is a valid expression by itself? 0159 compile(expr, "testexpr", "exec") 0160 0161 codestr = "@%s\ndef f(): pass" % expr 0162 self.assertRaises(SyntaxError, compile, codestr, "test", "exec") 0163 0164 # You can't put multiple decorators on a single line: 0165 # 0166 self.assertRaises(SyntaxError, compile, 0167 "@f1 @f2\ndef f(): pass", "test", "exec") 0168 0169 # Test runtime errors 0170 0171 def unimp(func): 0172 raise NotImplementedError 0173 context = dict(nullval=None, unimp=unimp) 0174 0175 for expr, exc in [ ("undef", NameError), 0176 ("nullval", TypeError), 0177 ("nullval.attr", AttributeError), 0178 ("unimp", NotImplementedError)]: 0179 codestr = "@%s\ndef f(): pass\nassert f() is None" % expr 0180 code = compile(codestr, "test", "exec") 0181 self.assertRaises(exc, eval, code, context) 0182 0183 def test_double(self): 0184 class C(object): 0185 @funcattrs(abc=1, xyz="haha") 0186 @funcattrs(booh=42) 0187 def foo(self): return 42 0188 self.assertEqual(C().foo(), 42) 0189 self.assertEqual(C.foo.abc, 1) 0190 self.assertEqual(C.foo.xyz, "haha") 0191 self.assertEqual(C.foo.booh, 42) 0192 0193 def test_order(self): 0194 # Test that decorators are applied in the proper order to the function 0195 # they are decorating. 0196 def callnum(num): 0197 """Decorator factory that returns a decorator that replaces the 0198 passed-in function with one that returns the value of 'num'""" 0199 def deco(func): 0200 return lambda: num 0201 return deco 0202 @callnum(2) 0203 @callnum(1) 0204 def foo(): return 42 0205 self.assertEqual(foo(), 2, 0206 "Application order of decorators is incorrect") 0207 0208 def test_eval_order(self): 0209 # Evaluating a decorated function involves four steps for each 0210 # decorator-maker (the function that returns a decorator): 0211 # 0212 # 1: Evaluate the decorator-maker name 0213 # 2: Evaluate the decorator-maker arguments (if any) 0214 # 3: Call the decorator-maker to make a decorator 0215 # 4: Call the decorator 0216 # 0217 # When there are multiple decorators, these steps should be 0218 # performed in the above order for each decorator, but we should 0219 # iterate through the decorators in the reverse of the order they 0220 # appear in the source. 0221 0222 actions = [] 0223 0224 def make_decorator(tag): 0225 actions.append('makedec' + tag) 0226 def decorate(func): 0227 actions.append('calldec' + tag) 0228 return func 0229 return decorate 0230 0231 class NameLookupTracer (object): 0232 def __init__(self, index): 0233 self.index = index 0234 0235 def __getattr__(self, fname): 0236 if fname == 'make_decorator': 0237 opname, res = ('evalname', make_decorator) 0238 elif fname == 'arg': 0239 opname, res = ('evalargs', str(self.index)) 0240 else: 0241 assert False, "Unknown attrname %s" % fname 0242 actions.append('%s%d' % (opname, self.index)) 0243 return res 0244 0245 c1, c2, c3 = map(NameLookupTracer, [ 1, 2, 3 ]) 0246 0247 expected_actions = [ 'evalname1', 'evalargs1', 'makedec1', 0248 'evalname2', 'evalargs2', 'makedec2', 0249 'evalname3', 'evalargs3', 'makedec3', 0250 'calldec3', 'calldec2', 'calldec1' ] 0251 0252 actions = [] 0253 @c1.make_decorator(c1.arg) 0254 @c2.make_decorator(c2.arg) 0255 @c3.make_decorator(c3.arg) 0256 def foo(): return 42 0257 self.assertEqual(foo(), 42) 0258 0259 self.assertEqual(actions, expected_actions) 0260 0261 # Test the equivalence claim in chapter 7 of the reference manual. 0262 # 0263 actions = [] 0264 def bar(): return 42 0265 bar = c1.make_decorator(c1.arg)(c2.make_decorator(c2.arg)(c3.make_decorator(c3.arg)(bar))) 0266 self.assertEqual(bar(), 42) 0267 self.assertEqual(actions, expected_actions) 0268 0269 def test_main(): 0270 test_support.run_unittest(TestDecorators) 0271 0272 if __name__=="__main__": 0273 test_main() 0274
Generated by PyXR 0.9.4