]>
Commit | Line | Data |
---|---|---|
4710c53d | 1 | """Support Eiffel-style preconditions and postconditions.\r |
2 | \r | |
3 | For example,\r | |
4 | \r | |
5 | class C:\r | |
6 | def m1(self, arg):\r | |
7 | require arg > 0\r | |
8 | return whatever\r | |
9 | ensure Result > arg\r | |
10 | \r | |
11 | can be written (clumsily, I agree) as:\r | |
12 | \r | |
13 | class C(Eiffel):\r | |
14 | def m1(self, arg):\r | |
15 | return whatever\r | |
16 | def m1_pre(self, arg):\r | |
17 | assert arg > 0\r | |
18 | def m1_post(self, Result, arg):\r | |
19 | assert Result > arg\r | |
20 | \r | |
21 | Pre- and post-conditions for a method, being implemented as methods\r | |
22 | themselves, are inherited independently from the method. This gives\r | |
23 | much of the same effect of Eiffel, where pre- and post-conditions are\r | |
24 | inherited when a method is overridden by a derived class. However,\r | |
25 | when a derived class in Python needs to extend a pre- or\r | |
26 | post-condition, it must manually merge the base class' pre- or\r | |
27 | post-condition with that defined in the derived class', for example:\r | |
28 | \r | |
29 | class D(C):\r | |
30 | def m1(self, arg):\r | |
31 | return arg**2\r | |
32 | def m1_post(self, Result, arg):\r | |
33 | C.m1_post(self, Result, arg)\r | |
34 | assert Result < 100\r | |
35 | \r | |
36 | This gives derived classes more freedom but also more responsibility\r | |
37 | than in Eiffel, where the compiler automatically takes care of this.\r | |
38 | \r | |
39 | In Eiffel, pre-conditions combine using contravariance, meaning a\r | |
40 | derived class can only make a pre-condition weaker; in Python, this is\r | |
41 | up to the derived class. For example, a derived class that takes away\r | |
42 | the requirement that arg > 0 could write:\r | |
43 | \r | |
44 | def m1_pre(self, arg):\r | |
45 | pass\r | |
46 | \r | |
47 | but one could equally write a derived class that makes a stronger\r | |
48 | requirement:\r | |
49 | \r | |
50 | def m1_pre(self, arg):\r | |
51 | require arg > 50\r | |
52 | \r | |
53 | It would be easy to modify the classes shown here so that pre- and\r | |
54 | post-conditions can be disabled (separately, on a per-class basis).\r | |
55 | \r | |
56 | A different design would have the pre- or post-condition testing\r | |
57 | functions return true for success and false for failure. This would\r | |
58 | make it possible to implement automatic combination of inherited\r | |
59 | and new pre-/post-conditions. All this is left as an exercise to the\r | |
60 | reader.\r | |
61 | \r | |
62 | """\r | |
63 | \r | |
64 | from Meta import MetaClass, MetaHelper, MetaMethodWrapper\r | |
65 | \r | |
66 | class EiffelMethodWrapper(MetaMethodWrapper):\r | |
67 | \r | |
68 | def __init__(self, func, inst):\r | |
69 | MetaMethodWrapper.__init__(self, func, inst)\r | |
70 | # Note that the following causes recursive wrappers around\r | |
71 | # the pre-/post-condition testing methods. These are harmless\r | |
72 | # but inefficient; to avoid them, the lookup must be done\r | |
73 | # using the class.\r | |
74 | try:\r | |
75 | self.pre = getattr(inst, self.__name__ + "_pre")\r | |
76 | except AttributeError:\r | |
77 | self.pre = None\r | |
78 | try:\r | |
79 | self.post = getattr(inst, self.__name__ + "_post")\r | |
80 | except AttributeError:\r | |
81 | self.post = None\r | |
82 | \r | |
83 | def __call__(self, *args, **kw):\r | |
84 | if self.pre:\r | |
85 | apply(self.pre, args, kw)\r | |
86 | Result = apply(self.func, (self.inst,) + args, kw)\r | |
87 | if self.post:\r | |
88 | apply(self.post, (Result,) + args, kw)\r | |
89 | return Result\r | |
90 | \r | |
91 | class EiffelHelper(MetaHelper):\r | |
92 | __methodwrapper__ = EiffelMethodWrapper\r | |
93 | \r | |
94 | class EiffelMetaClass(MetaClass):\r | |
95 | __helper__ = EiffelHelper\r | |
96 | \r | |
97 | Eiffel = EiffelMetaClass('Eiffel', (), {})\r | |
98 | \r | |
99 | \r | |
100 | def _test():\r | |
101 | class C(Eiffel):\r | |
102 | def m1(self, arg):\r | |
103 | return arg+1\r | |
104 | def m1_pre(self, arg):\r | |
105 | assert arg > 0, "precondition for m1 failed"\r | |
106 | def m1_post(self, Result, arg):\r | |
107 | assert Result > arg\r | |
108 | x = C()\r | |
109 | x.m1(12)\r | |
110 | ## x.m1(-1)\r | |
111 | \r | |
112 | if __name__ == '__main__':\r | |
113 | _test()\r |