PyXR

c:\python24\lib \ test \ test_decorators.py



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
SourceForge.net Logo