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:
|
---|
32 | dict[m] = cls.make_eiffel_method(dict[m], pre, post)
|
---|
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)
|
---|