[2] | 1 | """Support Eiffel-style preconditions and postconditions."""
|
---|
| 2 |
|
---|
| 3 | from types import FunctionType as function
|
---|
| 4 |
|
---|
| 5 | class EiffelBaseMetaClass(type):
|
---|
| 6 |
|
---|
| 7 | def __new__(meta, name, bases, dict):
|
---|
| 8 | meta.convert_methods(dict)
|
---|
| 9 | return super(EiffelBaseMetaClass, meta).__new__(meta, name, bases,
|
---|
| 10 | dict)
|
---|
| 11 |
|
---|
| 12 | @classmethod
|
---|
| 13 | def convert_methods(cls, dict):
|
---|
| 14 | """Replace functions in dict with EiffelMethod wrappers.
|
---|
| 15 |
|
---|
| 16 | The dict is modified in place.
|
---|
| 17 |
|
---|
| 18 | If a method ends in _pre or _post, it is removed from the dict
|
---|
| 19 | regardless of whether there is a corresponding method.
|
---|
| 20 | """
|
---|
| 21 | # find methods with pre or post conditions
|
---|
| 22 | methods = []
|
---|
| 23 | for k, v in dict.iteritems():
|
---|
| 24 | if k.endswith('_pre') or k.endswith('_post'):
|
---|
| 25 | assert isinstance(v, function)
|
---|
| 26 | elif isinstance(v, function):
|
---|
| 27 | methods.append(k)
|
---|
| 28 | for m in methods:
|
---|
| 29 | pre = dict.get("%s_pre" % m)
|
---|
| 30 | post = dict.get("%s_post" % m)
|
---|
| 31 | if pre or post:
|
---|
[391] | 32 | dict[m] = cls.make_eiffel_method(dict[m], pre, post)
|
---|
[2] | 33 |
|
---|
| 34 | class EiffelMetaClass1(EiffelBaseMetaClass):
|
---|
| 35 | # an implementation of the "eiffel" meta class that uses nested functions
|
---|
| 36 |
|
---|
| 37 | @staticmethod
|
---|
| 38 | def make_eiffel_method(func, pre, post):
|
---|
| 39 | def method(self, *args, **kwargs):
|
---|
| 40 | if pre:
|
---|
| 41 | pre(self, *args, **kwargs)
|
---|
| 42 | x = func(self, *args, **kwargs)
|
---|
| 43 | if post:
|
---|
| 44 | post(self, x, *args, **kwargs)
|
---|
| 45 | return x
|
---|
| 46 |
|
---|
| 47 | if func.__doc__:
|
---|
| 48 | method.__doc__ = func.__doc__
|
---|
| 49 |
|
---|
| 50 | return method
|
---|
| 51 |
|
---|
| 52 | class EiffelMethodWrapper:
|
---|
| 53 |
|
---|
| 54 | def __init__(self, inst, descr):
|
---|
| 55 | self._inst = inst
|
---|
| 56 | self._descr = descr
|
---|
| 57 |
|
---|
| 58 | def __call__(self, *args, **kwargs):
|
---|
| 59 | return self._descr.callmethod(self._inst, args, kwargs)
|
---|
| 60 |
|
---|
| 61 | class EiffelDescriptor(object):
|
---|
| 62 |
|
---|
| 63 | def __init__(self, func, pre, post):
|
---|
| 64 | self._func = func
|
---|
| 65 | self._pre = pre
|
---|
| 66 | self._post = post
|
---|
| 67 |
|
---|
| 68 | self.__name__ = func.__name__
|
---|
| 69 | self.__doc__ = func.__doc__
|
---|
| 70 |
|
---|
| 71 | def __get__(self, obj, cls):
|
---|
| 72 | return EiffelMethodWrapper(obj, self)
|
---|
| 73 |
|
---|
| 74 | def callmethod(self, inst, args, kwargs):
|
---|
| 75 | if self._pre:
|
---|
| 76 | self._pre(inst, *args, **kwargs)
|
---|
| 77 | x = self._func(inst, *args, **kwargs)
|
---|
| 78 | if self._post:
|
---|
| 79 | self._post(inst, x, *args, **kwargs)
|
---|
| 80 | return x
|
---|
| 81 |
|
---|
| 82 | class EiffelMetaClass2(EiffelBaseMetaClass):
|
---|
| 83 | # an implementation of the "eiffel" meta class that uses descriptors
|
---|
| 84 |
|
---|
| 85 | make_eiffel_method = EiffelDescriptor
|
---|
| 86 |
|
---|
| 87 | def _test(metaclass):
|
---|
| 88 | class Eiffel:
|
---|
| 89 | __metaclass__ = metaclass
|
---|
| 90 |
|
---|
| 91 | class Test(Eiffel):
|
---|
| 92 |
|
---|
| 93 | def m(self, arg):
|
---|
| 94 | """Make it a little larger"""
|
---|
| 95 | return arg + 1
|
---|
| 96 |
|
---|
| 97 | def m2(self, arg):
|
---|
| 98 | """Make it a little larger"""
|
---|
| 99 | return arg + 1
|
---|
| 100 |
|
---|
| 101 | def m2_pre(self, arg):
|
---|
| 102 | assert arg > 0
|
---|
| 103 |
|
---|
| 104 | def m2_post(self, result, arg):
|
---|
| 105 | assert result > arg
|
---|
| 106 |
|
---|
| 107 | class Sub(Test):
|
---|
| 108 | def m2(self, arg):
|
---|
| 109 | return arg**2
|
---|
| 110 | def m2_post(self, Result, arg):
|
---|
| 111 | super(Sub, self).m2_post(Result, arg)
|
---|
| 112 | assert Result < 100
|
---|
| 113 |
|
---|
| 114 | t = Test()
|
---|
| 115 | t.m(1)
|
---|
| 116 | t.m2(1)
|
---|
| 117 | try:
|
---|
| 118 | t.m2(0)
|
---|
| 119 | except AssertionError:
|
---|
| 120 | pass
|
---|
| 121 | else:
|
---|
| 122 | assert False
|
---|
| 123 |
|
---|
| 124 | s = Sub()
|
---|
| 125 | try:
|
---|
| 126 | s.m2(1)
|
---|
| 127 | except AssertionError:
|
---|
| 128 | pass # result == arg
|
---|
| 129 | else:
|
---|
| 130 | assert False
|
---|
| 131 | try:
|
---|
| 132 | s.m2(10)
|
---|
| 133 | except AssertionError:
|
---|
| 134 | pass # result == 100
|
---|
| 135 | else:
|
---|
| 136 | assert False
|
---|
| 137 | s.m2(5)
|
---|
| 138 |
|
---|
| 139 | if __name__ == "__main__":
|
---|
| 140 | _test(EiffelMetaClass1)
|
---|
| 141 | _test(EiffelMetaClass2)
|
---|