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