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