|
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() |