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