blob: 1dd7e6c17f04e078b672d35ab0d746deda00dd73 [file] [log] [blame]
"""Support Eiffel-style preconditions and postconditions."""
from new import function
class EiffelBaseMetaClass(type):
def __new__(meta, name, bases, dict):
meta.convert_methods(dict)
return super(EiffelBaseMetaClass, meta).__new__(meta, name, bases,
dict)
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)
convert_methods = classmethod(convert_methods)
class EiffelMetaClass1(EiffelBaseMetaClass):
# an implementation of the "eiffel" meta class that uses nested functions
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
make_eiffel_method = staticmethod(make_eiffel_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)