Minor documentation change - hyperlink tidied up.
[python.git] / Demo / metaclasses / Eiffel.py
blob24fac148de0f6001b221b8fe858fb9bf915d2016
1 """Support Eiffel-style preconditions and postconditions.
3 For example,
5 class C:
6 def m1(self, arg):
7 require arg > 0
8 return whatever
9 ensure Result > arg
11 can be written (clumsily, I agree) as:
13 class 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
21 Pre- and post-conditions for a method, being implemented as methods
22 themselves, are inherited independently from the method. This gives
23 much of the same effect of Eiffel, where pre- and post-conditions are
24 inherited when a method is overridden by a derived class. However,
25 when a derived class in Python needs to extend a pre- or
26 post-condition, it must manually merge the base class' pre- or
27 post-condition with that defined in the derived class', for example:
29 class D(C):
30 def m1(self, arg):
31 return arg**2
32 def m1_post(self, Result, arg):
33 C.m1_post(self, Result, arg)
34 assert Result < 100
36 This gives derived classes more freedom but also more responsibility
37 than in Eiffel, where the compiler automatically takes care of this.
39 In Eiffel, pre-conditions combine using contravariance, meaning a
40 derived class can only make a pre-condition weaker; in Python, this is
41 up to the derived class. For example, a derived class that takes away
42 the requirement that arg > 0 could write:
44 def m1_pre(self, arg):
45 pass
47 but one could equally write a derived class that makes a stronger
48 requirement:
50 def m1_pre(self, arg):
51 require arg > 50
53 It would be easy to modify the classes shown here so that pre- and
54 post-conditions can be disabled (separately, on a per-class basis).
56 A different design would have the pre- or post-condition testing
57 functions return true for success and false for failure. This would
58 make it possible to implement automatic combination of inherited
59 and new pre-/post-conditions. All this is left as an exercise to the
60 reader.
62 """
64 from Meta import MetaClass, MetaHelper, MetaMethodWrapper
66 class EiffelMethodWrapper(MetaMethodWrapper):
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
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
91 class EiffelHelper(MetaHelper):
92 __methodwrapper__ = EiffelMethodWrapper
94 class EiffelMetaClass(MetaClass):
95 __helper__ = EiffelHelper
97 Eiffel = EiffelMetaClass('Eiffel', (), {})
100 def _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)
110 ## x.m1(-1)
112 if __name__ == '__main__':
113 _test()