1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 | Lib/test/test_decorators.py
import unittest from test import test_support def funcattrs(**kwds): def decorate(func): func.__dict__.update(kwds) return func return decorate class MiscDecorators (object): @staticmethod def author(name): def decorate(func): func.__dict__['author'] = name return func return decorate # ----------------------------------------------- class DbcheckError (Exception): def __init__(self, exprstr, func, args, kwds): # A real version of this would set attributes here Exception.__init__(self, "dbcheck %r failed (func=%s args=%s kwds=%s)" % (exprstr, func, args, kwds)) def dbcheck(exprstr, globals=None, locals=None): "Decorator to implement debugging assertions" def decorate(func): expr = compile(exprstr, "dbcheck-%s" % func.func_name, "eval") def check(*args, **kwds): if not eval(expr, globals, locals): raise DbcheckError(exprstr, func, args, kwds) return func(*args, **kwds) return check return decorate # ----------------------------------------------- def countcalls(counts): "Decorator to count calls to a function" def decorate(func): func_name = func.func_name counts[func_name] = 0 def call(*args, **kwds): counts[func_name] += 1 return func(*args, **kwds) call.func_name = func_name return call return decorate # ----------------------------------------------- def memoize(func): saved = {} def call(*args): try: return saved[args] except KeyError: res = func(*args) saved[args] = res return res except TypeError: # Unhashable argument return func(*args) call.func_name = func.func_name return call # ----------------------------------------------- class TestDecorators(unittest.TestCase): def test_single(self): class C(object): @staticmethod def foo(): return 42 self.assertEqual(C.foo(), 42) self.assertEqual(C().foo(), 42) def test_staticmethod_function(self): @staticmethod def notamethod(x): return x self.assertRaises(TypeError, notamethod, 1) def test_dotted(self): decorators = MiscDecorators() @decorators.author('Cleese') def foo(): return 42 self.assertEqual(foo(), 42) self.assertEqual(foo.author, 'Cleese') def test_argforms(self): # A few tests of argument passing, as we use restricted form # of expressions for decorators. def noteargs(*args, **kwds): def decorate(func): setattr(func, 'dbval', (args, kwds)) return func return decorate args = ( 'Now', 'is', 'the', 'time' ) kwds = dict(one=1, two=2) @noteargs(*args, **kwds) def f1(): return 42 self.assertEqual(f1(), 42) self.assertEqual(f1.dbval, (args, kwds)) @noteargs('terry', 'gilliam', eric='idle', john='cleese') def f2(): return 84 self.assertEqual(f2(), 84) self.assertEqual(f2.dbval, (('terry', 'gilliam'), dict(eric='idle', john='cleese'))) @noteargs(1, 2,) def f3(): pass self.assertEqual(f3.dbval, ((1, 2), {})) def test_dbcheck(self): @dbcheck('args[1] is not None') def f(a, b): return a + b self.assertEqual(f(1, 2), 3) self.assertRaises(DbcheckError, f, 1, None) def test_memoize(self): counts = {} @memoize @countcalls(counts) def double(x): return x * 2 self.assertEqual(double.func_name, 'double') self.assertEqual(counts, dict(double=0)) # Only the first call with a given argument bumps the call count: # self.assertEqual(double(2), 4) self.assertEqual(counts['double'], 1) self.assertEqual(double(2), 4) self.assertEqual(counts['double'], 1) self.assertEqual(double(3), 6) self.assertEqual(counts['double'], 2) # Unhashable arguments do not get memoized: # self.assertEqual(double([10]), [10, 10]) self.assertEqual(counts['double'], 3) self.assertEqual(double([10]), [10, 10]) self.assertEqual(counts['double'], 4) def test_errors(self): # Test syntax restrictions - these are all compile-time errors: # for expr in [ "1+2", "x[3]", "(1, 2)" ]: # Sanity check: is expr is a valid expression by itself? compile(expr, "testexpr", "exec") 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): raise NotImplementedError context = dict(nullval=None, unimp=unimp) for expr, exc in [ ("undef", NameError), ("nullval", TypeError), ("nullval.attr", AttributeError), ("unimp", NotImplementedError)]: codestr = "@%s\ndef f(): pass\nassert f() is None" % expr code = compile(codestr, "test", "exec") self.assertRaises(exc, eval, code, context) def test_double(self): class C(object): @funcattrs(abc=1, xyz="haha") @funcattrs(booh=42) def foo(self): return 42 self.assertEqual(C().foo(), 42) self.assertEqual(C.foo.abc, 1) self.assertEqual(C.foo.xyz, "haha") self.assertEqual(C.foo.booh, 42) def test_order(self): # Test that decorators are applied in the proper order to the function # they are decorating. def callnum(num): """Decorator factory that returns a decorator that replaces the passed-in function with one that returns the value of 'num'""" def deco(func): return lambda: num return deco @callnum(2) @callnum(1) def foo(): return 42 self.assertEqual(foo(), 2, "Application order of decorators is incorrect") 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) class TestClassDecorators(unittest.TestCase): def test_simple(self): def plain(x): x.extra = 'Hello' return x @plain class C(object): pass self.assertEqual(C.extra, 'Hello') def test_double(self): def ten(x): x.extra = 10 return x def add_five(x): x.extra += 5 return x @add_five @ten class C(object): pass self.assertEqual(C.extra, 15) def test_order(self): def applied_first(x): x.extra = 'first' return x def applied_second(x): x.extra = 'second' return x @applied_second @applied_first class C(object): pass self.assertEqual(C.extra, 'second') def test_main(): test_support.run_unittest(TestDecorators) test_support.run_unittest(TestClassDecorators) if __name__=="__main__": test_main() |