/Demo/newmetaclasses/Eiffel.py

http://unladen-swallow.googlecode.com/ · Python · 141 lines · 109 code · 21 blank · 11 comment · 12 complexity · bde1de96db24147988b8440c15bf7019 MD5 · raw file

  1. """Support Eiffel-style preconditions and postconditions."""
  2. from types import FunctionType as function
  3. class EiffelBaseMetaClass(type):
  4. def __new__(meta, name, bases, dict):
  5. meta.convert_methods(dict)
  6. return super(EiffelBaseMetaClass, meta).__new__(meta, name, bases,
  7. dict)
  8. @classmethod
  9. def convert_methods(cls, dict):
  10. """Replace functions in dict with EiffelMethod wrappers.
  11. The dict is modified in place.
  12. If a method ends in _pre or _post, it is removed from the dict
  13. regardless of whether there is a corresponding method.
  14. """
  15. # find methods with pre or post conditions
  16. methods = []
  17. for k, v in dict.iteritems():
  18. if k.endswith('_pre') or k.endswith('_post'):
  19. assert isinstance(v, function)
  20. elif isinstance(v, function):
  21. methods.append(k)
  22. for m in methods:
  23. pre = dict.get("%s_pre" % m)
  24. post = dict.get("%s_post" % m)
  25. if pre or post:
  26. dict[k] = cls.make_eiffel_method(dict[m], pre, post)
  27. class EiffelMetaClass1(EiffelBaseMetaClass):
  28. # an implementation of the "eiffel" meta class that uses nested functions
  29. @staticmethod
  30. def make_eiffel_method(func, pre, post):
  31. def method(self, *args, **kwargs):
  32. if pre:
  33. pre(self, *args, **kwargs)
  34. x = func(self, *args, **kwargs)
  35. if post:
  36. post(self, x, *args, **kwargs)
  37. return x
  38. if func.__doc__:
  39. method.__doc__ = func.__doc__
  40. return method
  41. class EiffelMethodWrapper:
  42. def __init__(self, inst, descr):
  43. self._inst = inst
  44. self._descr = descr
  45. def __call__(self, *args, **kwargs):
  46. return self._descr.callmethod(self._inst, args, kwargs)
  47. class EiffelDescriptor(object):
  48. def __init__(self, func, pre, post):
  49. self._func = func
  50. self._pre = pre
  51. self._post = post
  52. self.__name__ = func.__name__
  53. self.__doc__ = func.__doc__
  54. def __get__(self, obj, cls):
  55. return EiffelMethodWrapper(obj, self)
  56. def callmethod(self, inst, args, kwargs):
  57. if self._pre:
  58. self._pre(inst, *args, **kwargs)
  59. x = self._func(inst, *args, **kwargs)
  60. if self._post:
  61. self._post(inst, x, *args, **kwargs)
  62. return x
  63. class EiffelMetaClass2(EiffelBaseMetaClass):
  64. # an implementation of the "eiffel" meta class that uses descriptors
  65. make_eiffel_method = EiffelDescriptor
  66. def _test(metaclass):
  67. class Eiffel:
  68. __metaclass__ = metaclass
  69. class Test(Eiffel):
  70. def m(self, arg):
  71. """Make it a little larger"""
  72. return arg + 1
  73. def m2(self, arg):
  74. """Make it a little larger"""
  75. return arg + 1
  76. def m2_pre(self, arg):
  77. assert arg > 0
  78. def m2_post(self, result, arg):
  79. assert result > arg
  80. class Sub(Test):
  81. def m2(self, arg):
  82. return arg**2
  83. def m2_post(self, Result, arg):
  84. super(Sub, self).m2_post(Result, arg)
  85. assert Result < 100
  86. t = Test()
  87. t.m(1)
  88. t.m2(1)
  89. try:
  90. t.m2(0)
  91. except AssertionError:
  92. pass
  93. else:
  94. assert False
  95. s = Sub()
  96. try:
  97. s.m2(1)
  98. except AssertionError:
  99. pass # result == arg
  100. else:
  101. assert False
  102. try:
  103. s.m2(10)
  104. except AssertionError:
  105. pass # result == 100
  106. else:
  107. assert False
  108. s.m2(5)
  109. if __name__ == "__main__":
  110. _test(EiffelMetaClass1)
  111. _test(EiffelMetaClass2)