summaryrefslogtreecommitdiffstats
path: root/Tools/demo/eiffel.py
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/demo/eiffel.py')
-rwxr-xr-xTools/demo/eiffel.py146
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()