|
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[k] = 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) |