Home | History | Annotate | Download | only in metaclasses
      1 """Tracing metaclass.
      2 
      3 XXX This is very much a work in progress.
      4 
      5 """
      6 
      7 import types, sys
      8 
      9 class TraceMetaClass:
     10     """Metaclass for tracing.
     11 
     12     Classes defined using this metaclass have an automatic tracing
     13     feature -- by setting the __trace_output__ instance (or class)
     14     variable to a file object, trace messages about all calls are
     15     written to the file.  The trace formatting can be changed by
     16     defining a suitable __trace_call__ method.
     17 
     18     """
     19 
     20     __inited = 0
     21 
     22     def __init__(self, name, bases, dict):
     23         self.__name__ = name
     24         self.__bases__ = bases
     25         self.__dict = dict
     26         # XXX Can't define __dict__, alas
     27         self.__inited = 1
     28 
     29     def __getattr__(self, name):
     30         try:
     31             return self.__dict[name]
     32         except KeyError:
     33             for base in self.__bases__:
     34                 try:
     35                     return base.__getattr__(name)
     36                 except AttributeError:
     37                     pass
     38             raise AttributeError, name
     39 
     40     def __setattr__(self, name, value):
     41         if not self.__inited:
     42             self.__dict__[name] = value
     43         else:
     44             self.__dict[name] = value
     45 
     46     def __call__(self, *args, **kw):
     47         inst = TracingInstance()
     48         inst.__meta_init__(self)
     49         try:
     50             init = inst.__getattr__('__init__')
     51         except AttributeError:
     52             init = lambda: None
     53         apply(init, args, kw)
     54         return inst
     55 
     56     __trace_output__ = None
     57 
     58 class TracingInstance:
     59     """Helper class to represent an instance of a tracing class."""
     60 
     61     def __trace_call__(self, fp, fmt, *args):
     62         fp.write((fmt+'\n') % args)
     63 
     64     def __meta_init__(self, klass):
     65         self.__class = klass
     66 
     67     def __getattr__(self, name):
     68         # Invoked for any attr not in the instance's __dict__
     69         try:
     70             raw = self.__class.__getattr__(name)
     71         except AttributeError:
     72             raise AttributeError, name
     73         if type(raw) != types.FunctionType:
     74             return raw
     75         # It's a function
     76         fullname = self.__class.__name__ + "." + name
     77         if not self.__trace_output__ or name == '__trace_call__':
     78             return NotTracingWrapper(fullname, raw, self)
     79         else:
     80             return TracingWrapper(fullname, raw, self)
     81 
     82 class NotTracingWrapper:
     83     def __init__(self, name, func, inst):
     84         self.__name__ = name
     85         self.func = func
     86         self.inst = inst
     87     def __call__(self, *args, **kw):
     88         return apply(self.func, (self.inst,) + args, kw)
     89 
     90 class TracingWrapper(NotTracingWrapper):
     91     def __call__(self, *args, **kw):
     92         self.inst.__trace_call__(self.inst.__trace_output__,
     93                                  "calling %s, inst=%s, args=%s, kw=%s",
     94                                  self.__name__, self.inst, args, kw)
     95         try:
     96             rv = apply(self.func, (self.inst,) + args, kw)
     97         except:
     98             t, v, tb = sys.exc_info()
     99             self.inst.__trace_call__(self.inst.__trace_output__,
    100                                      "returning from %s with exception %s: %s",
    101                                      self.__name__, t, v)
    102             raise t, v, tb
    103         else:
    104             self.inst.__trace_call__(self.inst.__trace_output__,
    105                                      "returning from %s with value %s",
    106                                      self.__name__, rv)
    107             return rv
    108 
    109 Traced = TraceMetaClass('Traced', (), {'__trace_output__': None})
    110 
    111 
    112 def _test():
    113     global C, D
    114     class C(Traced):
    115         def __init__(self, x=0): self.x = x
    116         def m1(self, x): self.x = x
    117         def m2(self, y): return self.x + y
    118         __trace_output__ = sys.stdout
    119     class D(C):
    120         def m2(self, y): print "D.m2(%r)" % (y,); return C.m2(self, y)
    121         __trace_output__ = None
    122     x = C(4321)
    123     print x
    124     print x.x
    125     print x.m1(100)
    126     print x.m1(10)
    127     print x.m2(33)
    128     print x.m1(5)
    129     print x.m2(4000)
    130     print x.x
    131 
    132     print C.__init__
    133     print C.m2
    134     print D.__init__
    135     print D.m2
    136 
    137     y = D()
    138     print y
    139     print y.m1(10)
    140     print y.m2(100)
    141     print y.x
    142 
    143 if __name__ == '__main__':
    144     _test()
    145