Home | History | Annotate | Download | only in newmetaclasses
      1 """Support Eiffel-style preconditions and postconditions."""
      2 
      3 from types import FunctionType as function
      4 
      5 class EiffelBaseMetaClass(type):
      6 
      7     def __new__(meta, name, bases, dict):
      8         meta.convert_methods(dict)
      9         return super(EiffelBaseMetaClass, meta).__new__(meta, name, bases,
     10                                                         dict)
     11 
     12     @classmethod
     13     def convert_methods(cls, dict):
     14         """Replace functions in dict with EiffelMethod wrappers.
     15 
     16         The dict is modified in place.
     17 
     18         If a method ends in _pre or _post, it is removed from the dict
     19         regardless of whether there is a corresponding method.
     20         """
     21         # find methods with pre or post conditions

     22         methods = []
     23         for k, v in dict.iteritems():
     24             if k.endswith('_pre') or k.endswith('_post'):
     25                 assert isinstance(v, function)
     26             elif isinstance(v, function):
     27                 methods.append(k)
     28         for m in methods:
     29             pre = dict.get("%s_pre" % m)
     30             post = dict.get("%s_post" % m)
     31             if pre or post:
     32                 dict[k] = cls.make_eiffel_method(dict[m], pre, post)
     33 
     34 class EiffelMetaClass1(EiffelBaseMetaClass):
     35     # an implementation of the "eiffel" meta class that uses nested functions

     36 
     37     @staticmethod
     38     def make_eiffel_method(func, pre, post):
     39         def method(self, *args, **kwargs):
     40             if pre:
     41                 pre(self, *args, **kwargs)
     42             x = func(self, *args, **kwargs)
     43             if post:
     44                 post(self, x, *args, **kwargs)
     45             return x
     46 
     47         if func.__doc__:
     48             method.__doc__ = func.__doc__
     49 
     50         return method
     51 
     52 class EiffelMethodWrapper:
     53 
     54     def __init__(self, inst, descr):
     55         self._inst = inst
     56         self._descr = descr
     57 
     58     def __call__(self, *args, **kwargs):
     59         return self._descr.callmethod(self._inst, args, kwargs)
     60 
     61 class EiffelDescriptor(object):
     62 
     63     def __init__(self, func, pre, post):
     64         self._func = func
     65         self._pre = pre
     66         self._post = post
     67 
     68         self.__name__ = func.__name__
     69         self.__doc__ = func.__doc__
     70 
     71     def __get__(self, obj, cls):
     72         return EiffelMethodWrapper(obj, self)
     73 
     74     def callmethod(self, inst, args, kwargs):
     75         if self._pre:
     76             self._pre(inst, *args, **kwargs)
     77         x = self._func(inst, *args, **kwargs)
     78         if self._post:
     79             self._post(inst, x, *args, **kwargs)
     80         return x
     81 
     82 class EiffelMetaClass2(EiffelBaseMetaClass):
     83     # an implementation of the "eiffel" meta class that uses descriptors

     84 
     85     make_eiffel_method = EiffelDescriptor
     86 
     87 def _test(metaclass):
     88     class Eiffel:
     89         __metaclass__ = metaclass
     90 
     91     class Test(Eiffel):
     92 
     93         def m(self, arg):
     94             """Make it a little larger"""
     95             return arg + 1
     96 
     97         def m2(self, arg):
     98             """Make it a little larger"""
     99             return arg + 1
    100 
    101         def m2_pre(self, arg):
    102             assert arg > 0
    103 
    104         def m2_post(self, result, arg):
    105             assert result > arg
    106 
    107     class Sub(Test):
    108         def m2(self, arg):
    109             return arg**2
    110         def m2_post(self, Result, arg):
    111             super(Sub, self).m2_post(Result, arg)
    112             assert Result < 100
    113 
    114     t = Test()
    115     t.m(1)
    116     t.m2(1)
    117     try:
    118         t.m2(0)
    119     except AssertionError:
    120         pass
    121     else:
    122         assert False
    123 
    124     s = Sub()
    125     try:
    126         s.m2(1)
    127     except AssertionError:
    128         pass # result == arg

    129     else:
    130         assert False
    131     try:
    132         s.m2(10)
    133     except AssertionError:
    134         pass # result ==  100

    135     else:
    136         assert False
    137     s.m2(5)
    138 
    139 if __name__ == "__main__":
    140     _test(EiffelMetaClass1)
    141     _test(EiffelMetaClass2)
    142