/Lib/test/test_decorators.py

http://unladen-swallow.googlecode.com/ · Python · 309 lines · 254 code · 34 blank · 21 comment · 6 complexity · b5043a4b22853c7305b3af7650ed4a03 MD5 · raw file

  1. import unittest
  2. from test import test_support
  3. def funcattrs(**kwds):
  4. def decorate(func):
  5. func.__dict__.update(kwds)
  6. return func
  7. return decorate
  8. class MiscDecorators (object):
  9. @staticmethod
  10. def author(name):
  11. def decorate(func):
  12. func.__dict__['author'] = name
  13. return func
  14. return decorate
  15. # -----------------------------------------------
  16. class DbcheckError (Exception):
  17. def __init__(self, exprstr, func, args, kwds):
  18. # A real version of this would set attributes here
  19. Exception.__init__(self, "dbcheck %r failed (func=%s args=%s kwds=%s)" %
  20. (exprstr, func, args, kwds))
  21. def dbcheck(exprstr, globals=None, locals=None):
  22. "Decorator to implement debugging assertions"
  23. def decorate(func):
  24. expr = compile(exprstr, "dbcheck-%s" % func.func_name, "eval")
  25. def check(*args, **kwds):
  26. if not eval(expr, globals, locals):
  27. raise DbcheckError(exprstr, func, args, kwds)
  28. return func(*args, **kwds)
  29. return check
  30. return decorate
  31. # -----------------------------------------------
  32. def countcalls(counts):
  33. "Decorator to count calls to a function"
  34. def decorate(func):
  35. func_name = func.func_name
  36. counts[func_name] = 0
  37. def call(*args, **kwds):
  38. counts[func_name] += 1
  39. return func(*args, **kwds)
  40. call.func_name = func_name
  41. return call
  42. return decorate
  43. # -----------------------------------------------
  44. def memoize(func):
  45. saved = {}
  46. def call(*args):
  47. try:
  48. return saved[args]
  49. except KeyError:
  50. res = func(*args)
  51. saved[args] = res
  52. return res
  53. except TypeError:
  54. # Unhashable argument
  55. return func(*args)
  56. call.func_name = func.func_name
  57. return call
  58. # -----------------------------------------------
  59. class TestDecorators(unittest.TestCase):
  60. def test_single(self):
  61. class C(object):
  62. @staticmethod
  63. def foo(): return 42
  64. self.assertEqual(C.foo(), 42)
  65. self.assertEqual(C().foo(), 42)
  66. def test_staticmethod_function(self):
  67. @staticmethod
  68. def notamethod(x):
  69. return x
  70. self.assertRaises(TypeError, notamethod, 1)
  71. def test_dotted(self):
  72. decorators = MiscDecorators()
  73. @decorators.author('Cleese')
  74. def foo(): return 42
  75. self.assertEqual(foo(), 42)
  76. self.assertEqual(foo.author, 'Cleese')
  77. def test_argforms(self):
  78. # A few tests of argument passing, as we use restricted form
  79. # of expressions for decorators.
  80. def noteargs(*args, **kwds):
  81. def decorate(func):
  82. setattr(func, 'dbval', (args, kwds))
  83. return func
  84. return decorate
  85. args = ( 'Now', 'is', 'the', 'time' )
  86. kwds = dict(one=1, two=2)
  87. @noteargs(*args, **kwds)
  88. def f1(): return 42
  89. self.assertEqual(f1(), 42)
  90. self.assertEqual(f1.dbval, (args, kwds))
  91. @noteargs('terry', 'gilliam', eric='idle', john='cleese')
  92. def f2(): return 84
  93. self.assertEqual(f2(), 84)
  94. self.assertEqual(f2.dbval, (('terry', 'gilliam'),
  95. dict(eric='idle', john='cleese')))
  96. @noteargs(1, 2,)
  97. def f3(): pass
  98. self.assertEqual(f3.dbval, ((1, 2), {}))
  99. def test_dbcheck(self):
  100. @dbcheck('args[1] is not None')
  101. def f(a, b):
  102. return a + b
  103. self.assertEqual(f(1, 2), 3)
  104. self.assertRaises(DbcheckError, f, 1, None)
  105. def test_memoize(self):
  106. counts = {}
  107. @memoize
  108. @countcalls(counts)
  109. def double(x):
  110. return x * 2
  111. self.assertEqual(double.func_name, 'double')
  112. self.assertEqual(counts, dict(double=0))
  113. # Only the first call with a given argument bumps the call count:
  114. #
  115. self.assertEqual(double(2), 4)
  116. self.assertEqual(counts['double'], 1)
  117. self.assertEqual(double(2), 4)
  118. self.assertEqual(counts['double'], 1)
  119. self.assertEqual(double(3), 6)
  120. self.assertEqual(counts['double'], 2)
  121. # Unhashable arguments do not get memoized:
  122. #
  123. self.assertEqual(double([10]), [10, 10])
  124. self.assertEqual(counts['double'], 3)
  125. self.assertEqual(double([10]), [10, 10])
  126. self.assertEqual(counts['double'], 4)
  127. def test_errors(self):
  128. # Test syntax restrictions - these are all compile-time errors:
  129. #
  130. for expr in [ "1+2", "x[3]", "(1, 2)" ]:
  131. # Sanity check: is expr is a valid expression by itself?
  132. compile(expr, "testexpr", "exec")
  133. codestr = "@%s\ndef f(): pass" % expr
  134. self.assertRaises(SyntaxError, compile, codestr, "test", "exec")
  135. # You can't put multiple decorators on a single line:
  136. #
  137. self.assertRaises(SyntaxError, compile,
  138. "@f1 @f2\ndef f(): pass", "test", "exec")
  139. # Test runtime errors
  140. def unimp(func):
  141. raise NotImplementedError
  142. context = dict(nullval=None, unimp=unimp)
  143. for expr, exc in [ ("undef", NameError),
  144. ("nullval", TypeError),
  145. ("nullval.attr", AttributeError),
  146. ("unimp", NotImplementedError)]:
  147. codestr = "@%s\ndef f(): pass\nassert f() is None" % expr
  148. code = compile(codestr, "test", "exec")
  149. self.assertRaises(exc, eval, code, context)
  150. def test_double(self):
  151. class C(object):
  152. @funcattrs(abc=1, xyz="haha")
  153. @funcattrs(booh=42)
  154. def foo(self): return 42
  155. self.assertEqual(C().foo(), 42)
  156. self.assertEqual(C.foo.abc, 1)
  157. self.assertEqual(C.foo.xyz, "haha")
  158. self.assertEqual(C.foo.booh, 42)
  159. def test_order(self):
  160. # Test that decorators are applied in the proper order to the function
  161. # they are decorating.
  162. def callnum(num):
  163. """Decorator factory that returns a decorator that replaces the
  164. passed-in function with one that returns the value of 'num'"""
  165. def deco(func):
  166. return lambda: num
  167. return deco
  168. @callnum(2)
  169. @callnum(1)
  170. def foo(): return 42
  171. self.assertEqual(foo(), 2,
  172. "Application order of decorators is incorrect")
  173. def test_eval_order(self):
  174. # Evaluating a decorated function involves four steps for each
  175. # decorator-maker (the function that returns a decorator):
  176. #
  177. # 1: Evaluate the decorator-maker name
  178. # 2: Evaluate the decorator-maker arguments (if any)
  179. # 3: Call the decorator-maker to make a decorator
  180. # 4: Call the decorator
  181. #
  182. # When there are multiple decorators, these steps should be
  183. # performed in the above order for each decorator, but we should
  184. # iterate through the decorators in the reverse of the order they
  185. # appear in the source.
  186. actions = []
  187. def make_decorator(tag):
  188. actions.append('makedec' + tag)
  189. def decorate(func):
  190. actions.append('calldec' + tag)
  191. return func
  192. return decorate
  193. class NameLookupTracer (object):
  194. def __init__(self, index):
  195. self.index = index
  196. def __getattr__(self, fname):
  197. if fname == 'make_decorator':
  198. opname, res = ('evalname', make_decorator)
  199. elif fname == 'arg':
  200. opname, res = ('evalargs', str(self.index))
  201. else:
  202. assert False, "Unknown attrname %s" % fname
  203. actions.append('%s%d' % (opname, self.index))
  204. return res
  205. c1, c2, c3 = map(NameLookupTracer, [ 1, 2, 3 ])
  206. expected_actions = [ 'evalname1', 'evalargs1', 'makedec1',
  207. 'evalname2', 'evalargs2', 'makedec2',
  208. 'evalname3', 'evalargs3', 'makedec3',
  209. 'calldec3', 'calldec2', 'calldec1' ]
  210. actions = []
  211. @c1.make_decorator(c1.arg)
  212. @c2.make_decorator(c2.arg)
  213. @c3.make_decorator(c3.arg)
  214. def foo(): return 42
  215. self.assertEqual(foo(), 42)
  216. self.assertEqual(actions, expected_actions)
  217. # Test the equivalence claim in chapter 7 of the reference manual.
  218. #
  219. actions = []
  220. def bar(): return 42
  221. bar = c1.make_decorator(c1.arg)(c2.make_decorator(c2.arg)(c3.make_decorator(c3.arg)(bar)))
  222. self.assertEqual(bar(), 42)
  223. self.assertEqual(actions, expected_actions)
  224. class TestClassDecorators(unittest.TestCase):
  225. def test_simple(self):
  226. def plain(x):
  227. x.extra = 'Hello'
  228. return x
  229. @plain
  230. class C(object): pass
  231. self.assertEqual(C.extra, 'Hello')
  232. def test_double(self):
  233. def ten(x):
  234. x.extra = 10
  235. return x
  236. def add_five(x):
  237. x.extra += 5
  238. return x
  239. @add_five
  240. @ten
  241. class C(object): pass
  242. self.assertEqual(C.extra, 15)
  243. def test_order(self):
  244. def applied_first(x):
  245. x.extra = 'first'
  246. return x
  247. def applied_second(x):
  248. x.extra = 'second'
  249. return x
  250. @applied_second
  251. @applied_first
  252. class C(object): pass
  253. self.assertEqual(C.extra, 'second')
  254. def test_main():
  255. test_support.run_unittest(TestDecorators)
  256. test_support.run_unittest(TestClassDecorators)
  257. if __name__=="__main__":
  258. test_main()