PageRenderTime 21ms CodeModel.GetById 13ms app.highlight 5ms RepoModel.GetById 1ms app.codeStats 0ms

/Demo/metaclasses/Eiffel.py

http://unladen-swallow.googlecode.com/
Python | 113 lines | 88 code | 0 blank | 25 comment | 1 complexity | 9c53b383bf86c9001f24cc42ed0165eb MD5 | raw file
  1"""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 arg**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)
110##    x.m1(-1)
111
112if __name__ == '__main__':
113    _test()