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