1#!/usr/bin/env python3 2 3""" 4Support Eiffel-style preconditions and postconditions for functions. 5 6An example for Python metaclasses. 7""" 8 9import unittest 10from types import FunctionType as function 11 12class EiffelBaseMetaClass(type): 13 14 def __new__(meta, name, bases, dict): 15 meta.convert_methods(dict) 16 return super(EiffelBaseMetaClass, meta).__new__( 17 meta, name, bases, dict) 18 19 @classmethod 20 def convert_methods(cls, dict): 21 """Replace functions in dict with EiffelMethod wrappers. 22 23 The dict is modified in place. 24 25 If a method ends in _pre or _post, it is removed from the dict 26 regardless of whether there is a corresponding method. 27 """ 28 # find methods with pre or post conditions 29 methods = [] 30 for k, v in dict.items(): 31 if k.endswith('_pre') or k.endswith('_post'): 32 assert isinstance(v, function) 33 elif isinstance(v, function): 34 methods.append(k) 35 for m in methods: 36 pre = dict.get("%s_pre" % m) 37 post = dict.get("%s_post" % m) 38 if pre or post: 39 dict[m] = cls.make_eiffel_method(dict[m], pre, post) 40 41 42class EiffelMetaClass1(EiffelBaseMetaClass): 43 # an implementation of the "eiffel" meta class that uses nested functions 44 45 @staticmethod 46 def make_eiffel_method(func, pre, post): 47 def method(self, *args, **kwargs): 48 if pre: 49 pre(self, *args, **kwargs) 50 rv = func(self, *args, **kwargs) 51 if post: 52 post(self, rv, *args, **kwargs) 53 return rv 54 55 if func.__doc__: 56 method.__doc__ = func.__doc__ 57 58 return method 59 60 61class EiffelMethodWrapper: 62 63 def __init__(self, inst, descr): 64 self._inst = inst 65 self._descr = descr 66 67 def __call__(self, *args, **kwargs): 68 return self._descr.callmethod(self._inst, args, kwargs) 69 70 71class EiffelDescriptor: 72 73 def __init__(self, func, pre, post): 74 self._func = func 75 self._pre = pre 76 self._post = post 77 78 self.__name__ = func.__name__ 79 self.__doc__ = func.__doc__ 80 81 def __get__(self, obj, cls=None): 82 return EiffelMethodWrapper(obj, self) 83 84 def callmethod(self, inst, args, kwargs): 85 if self._pre: 86 self._pre(inst, *args, **kwargs) 87 x = self._func(inst, *args, **kwargs) 88 if self._post: 89 self._post(inst, x, *args, **kwargs) 90 return x 91 92 93class EiffelMetaClass2(EiffelBaseMetaClass): 94 # an implementation of the "eiffel" meta class that uses descriptors 95 96 make_eiffel_method = EiffelDescriptor 97 98 99class Tests(unittest.TestCase): 100 101 def testEiffelMetaClass1(self): 102 self._test(EiffelMetaClass1) 103 104 def testEiffelMetaClass2(self): 105 self._test(EiffelMetaClass2) 106 107 def _test(self, metaclass): 108 class Eiffel(metaclass=metaclass): 109 pass 110 111 class Test(Eiffel): 112 def m(self, arg): 113 """Make it a little larger""" 114 return arg + 1 115 116 def m2(self, arg): 117 """Make it a little larger""" 118 return arg + 1 119 120 def m2_pre(self, arg): 121 assert arg > 0 122 123 def m2_post(self, result, arg): 124 assert result > arg 125 126 class Sub(Test): 127 def m2(self, arg): 128 return arg**2 129 130 def m2_post(self, Result, arg): 131 super(Sub, self).m2_post(Result, arg) 132 assert Result < 100 133 134 t = Test() 135 self.assertEqual(t.m(1), 2) 136 self.assertEqual(t.m2(1), 2) 137 self.assertRaises(AssertionError, t.m2, 0) 138 139 s = Sub() 140 self.assertRaises(AssertionError, s.m2, 1) 141 self.assertRaises(AssertionError, s.m2, 10) 142 self.assertEqual(s.m2(5), 25) 143 144 145if __name__ == "__main__": 146 unittest.main() 147