diff options
Diffstat (limited to 'Tools/demo/eiffel.py')
-rwxr-xr-x | Tools/demo/eiffel.py | 146 |
1 files changed, 146 insertions, 0 deletions
diff --git a/Tools/demo/eiffel.py b/Tools/demo/eiffel.py new file mode 100755 index 0000000..3a28224 --- /dev/null +++ b/Tools/demo/eiffel.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 + +""" +Support Eiffel-style preconditions and postconditions for functions. + +An example for Python metaclasses. +""" + +import unittest +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.items(): + 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) + rv = func(self, *args, **kwargs) + if post: + post(self, rv, *args, **kwargs) + return rv + + 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: + + 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 + + +class Tests(unittest.TestCase): + + def testEiffelMetaClass1(self): + self._test(EiffelMetaClass1) + + def testEiffelMetaClass2(self): + self._test(EiffelMetaClass2) + + def _test(self, metaclass): + class Eiffel(metaclass=metaclass): + pass + + 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() + self.assertEqual(t.m(1), 2) + self.assertEqual(t.m2(1), 2) + self.assertRaises(AssertionError, t.m2, 0) + + s = Sub() + self.assertRaises(AssertionError, s.m2, 1) + self.assertRaises(AssertionError, s.m2, 10) + self.assertEqual(s.m2(5), 25) + + +if __name__ == "__main__": + unittest.main() |