| """Support Eiffel-style preconditions and postconditions.""" | |
| from types import FunctionType as function | |
| class EiffelBaseMetaClass(type): | |
| def __new__(meta, name, bases, dict): | |
| meta.convert_methods(dict) | |
| return super(EiffelBaseMetaClass, meta).__new__(meta, name, bases, | |
| dict) | |
| @classmethod | |
| def convert_methods(cls, dict): | |
| """Replace functions in dict with EiffelMethod wrappers. | |
| The dict is modified in place. | |
| If a method ends in _pre or _post, it is removed from the dict | |
| regardless of whether there is a corresponding method. | |
| """ | |
| # find methods with pre or post conditions | |
| methods = [] | |
| for k, v in dict.iteritems(): | |
| if k.endswith('_pre') or k.endswith('_post'): | |
| assert isinstance(v, function) | |
| elif isinstance(v, function): | |
| methods.append(k) | |
| for m in methods: | |
| pre = dict.get("%s_pre" % m) | |
| post = dict.get("%s_post" % m) | |
| if pre or post: | |
| dict[k] = cls.make_eiffel_method(dict[m], pre, post) | |
| class EiffelMetaClass1(EiffelBaseMetaClass): | |
| # an implementation of the "eiffel" meta class that uses nested functions | |
| @staticmethod | |
| def make_eiffel_method(func, pre, post): | |
| def method(self, *args, **kwargs): | |
| if pre: | |
| pre(self, *args, **kwargs) | |
| x = func(self, *args, **kwargs) | |
| if post: | |
| post(self, x, *args, **kwargs) | |
| return x | |
| if func.__doc__: | |
| method.__doc__ = func.__doc__ | |
| return method | |
| class EiffelMethodWrapper: | |
| def __init__(self, inst, descr): | |
| self._inst = inst | |
| self._descr = descr | |
| def __call__(self, *args, **kwargs): | |
| return self._descr.callmethod(self._inst, args, kwargs) | |
| class EiffelDescriptor(object): | |
| def __init__(self, func, pre, post): | |
| self._func = func | |
| self._pre = pre | |
| self._post = post | |
| self.__name__ = func.__name__ | |
| self.__doc__ = func.__doc__ | |
| def __get__(self, obj, cls): | |
| return EiffelMethodWrapper(obj, self) | |
| def callmethod(self, inst, args, kwargs): | |
| if self._pre: | |
| self._pre(inst, *args, **kwargs) | |
| x = self._func(inst, *args, **kwargs) | |
| if self._post: | |
| self._post(inst, x, *args, **kwargs) | |
| return x | |
| class EiffelMetaClass2(EiffelBaseMetaClass): | |
| # an implementation of the "eiffel" meta class that uses descriptors | |
| make_eiffel_method = EiffelDescriptor | |
| def _test(metaclass): | |
| class Eiffel: | |
| __metaclass__ = metaclass | |
| class Test(Eiffel): | |
| def m(self, arg): | |
| """Make it a little larger""" | |
| return arg + 1 | |
| def m2(self, arg): | |
| """Make it a little larger""" | |
| return arg + 1 | |
| def m2_pre(self, arg): | |
| assert arg > 0 | |
| def m2_post(self, result, arg): | |
| assert result > arg | |
| class Sub(Test): | |
| def m2(self, arg): | |
| return arg**2 | |
| def m2_post(self, Result, arg): | |
| super(Sub, self).m2_post(Result, arg) | |
| assert Result < 100 | |
| t = Test() | |
| t.m(1) | |
| t.m2(1) | |
| try: | |
| t.m2(0) | |
| except AssertionError: | |
| pass | |
| else: | |
| assert False | |
| s = Sub() | |
| try: | |
| s.m2(1) | |
| except AssertionError: | |
| pass # result == arg | |
| else: | |
| assert False | |
| try: | |
| s.m2(10) | |
| except AssertionError: | |
| pass # result == 100 | |
| else: | |
| assert False | |
| s.m2(5) | |
| if __name__ == "__main__": | |
| _test(EiffelMetaClass1) | |
| _test(EiffelMetaClass2) |