blob: d9c8a9a0c7f95150cc13dd0cf912bedec81a707a [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):
Guido van Rossum4117e541998-09-14 16:44:15 +00007 require arg > 0
8 return whatever
Guido van Rossum27e4aa31997-08-25 15:37:59 +00009 ensure Result > arg
10
11can be written (clumsily, I agree) as:
12
13class C(Eiffel):
14 def m1(self, arg):
Guido van Rossum4117e541998-09-14 16:44:15 +000015 return whatever
Guido van Rossum27e4aa31997-08-25 15:37:59 +000016 def m1_pre(self, arg):
Guido van Rossum4117e541998-09-14 16:44:15 +000017 assert arg > 0
Guido van Rossum27e4aa31997-08-25 15:37:59 +000018 def m1_post(self, Result, arg):
Guido van Rossum4117e541998-09-14 16:44:15 +000019 assert Result > arg
Guido van Rossum27e4aa31997-08-25 15:37:59 +000020
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):
Guido van Rossum4117e541998-09-14 16:44:15 +000031 return whatever**2
Guido van Rossum27e4aa31997-08-25 15:37:59 +000032 def m1_post(self, Result, arg):
Guido van Rossum4117e541998-09-14 16:44:15 +000033 C.m1_post(self, Result, arg)
34 assert Result < 100
Guido van Rossum27e4aa31997-08-25 15:37:59 +000035
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):
Guido van Rossum4117e541998-09-14 16:44:15 +000045 pass
Guido van Rossum27e4aa31997-08-25 15:37:59 +000046
47but one could equally write a derived class that makes a stronger
48requirement:
49
50 def m1_pre(self, arg):
Guido van Rossum4117e541998-09-14 16:44:15 +000051 require arg > 50
Guido van Rossum27e4aa31997-08-25 15:37:59 +000052
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):
Guido van Rossum4117e541998-09-14 16:44:15 +000069 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
Guido van Rossum27e4aa31997-08-25 15:37:59 +000082
83 def __call__(self, *args, **kw):
Guido van Rossum4117e541998-09-14 16:44:15 +000084 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
Guido van Rossum27e4aa31997-08-25 15:37:59 +000090
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):
Guido van Rossum4117e541998-09-14 16:44:15 +0000102 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
Guido van Rossum27e4aa31997-08-25 15:37:59 +0000108 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()