Matthew Palmer: A trace mixin for Python
Although I am by means an expert Python programmer, I do dabble -- at the
moment, I'm dabbling fairly heavily in a project for work. I had need, as
you do on occasion, for a call trace (I called this method, it returned X,
I then called this other method, it returned Y, and so on). Astoundingly,
I got no answer when I asked on #python, as I would have expected
this sort of thing to be a gimme -- even possibly including it as a core
language feature. But nary an answer was forthcoming. Searches for mixins
or magic function calls which might solve my problem were similarly
fruitless.
I resigned myself to manually sticking print wrappers around the function
calls of interest, but that got old real fast (especially after I spent
about a half-hour chasing my tail because I'd forgotten to return the return
value after I printed it).
As usual, my subconscious saved the day. On the train home this afternoon,
I suddenly had a brainwave, and the code which I've reproduced below
appeared almost fully-formed at my fingertips. It's times like this that I
really enjoy programming.
Now, of course, someone will pop up and say "oh here it is over here", or
"you just set the __trace_me_baby__ attribute on your object", but I can
console myself with the fact that I know a lot more about grubby Python
internals than I did before.
The code: (a pre-saved
version is also available).
# tracemixin.py
#
# A python mixin to trace the calling of methods.
#
# Copyright (C) 2005 Matthew Palmer
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
# or see http://www.gnu.org/licenses/gpl.html
#
"""
A mixin class to permit the tracing of method calls on an object.
This class, when mixed in, overrides the default operation of method calls
so that requests to call methods which have been listed in the
self._traced_methods list will be printed with their arguments and return
values. Also included is a simple call depth indication, using indents.
Tracing of particular methods can be turned on and off on a per-object basis
by simply including or removing the method's name in the _traced_methods
list on the particular object. Whole classes can be traced by setting
the list as a class attribute.
To include this class, simply make it the first class name in the list of
parent classes when defining a new class, like so:
class Something(TraceMixin, RealParentClass):
You could probably leave the TraceMixin in your production code, although
it'll slow execution down by a bit.
Complete usage example:
from tracemixin import TraceMixin
class RandomTask(TraceMixin):
def x(self):
print "X!"
def y(self, arg):
print "%s Y!" % arg
self.x()
x = RandomTask()
x.x()
x._traced_methods = ['x', 'y']
x.y('why?')
Output:
X!
::y(why?)
why? Y!
::x()
X!
=>None
=>None
"""
class TraceMixin(object):
_traced_methods = []
_depth = 0
def __getattribute__(self, attr):
if attr in object.__getattribute__(self, '_traced_methods'):
self.method = attr
attr = '_trace_proxy'
return object.__getattribute__(self, attr)
def _trace_proxy(self, *args):
realmethod = object.__getattribute__(self, self.method)
if realmethod and realmethod.__class__ == self._trace_proxy.__class__:
print "%s::%s(%s)" % (self._depth*' ', self.method, ', '.join(args))
self._depth += 1
rv = realmethod(*args)
self._depth -= 1
print "%s=>%s" % (self._depth*' ', rv)
return rv
else:
raise AttributeError



