| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- #!/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[m] = 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()
|