|
|
|
|
|
""" |
|
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. |
|
""" |
|
|
|
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): |
|
|
|
|
|
@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=None): |
|
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): |
|
|
|
|
|
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() |
|
|