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