Credit: Luther Blissett
You want to control the behavior of a class object and all of its instance objects, paying minimal runtime costs.
Python 2.2 lets you easily define your own custom metaclasses so you can build class objects whose behavior is entirely under your control, without runtime overhead except when the classes are created. For example, if you want to ensure that all methods a class defines (but not those it inherits and doesn’t override) are traced, a custom metaclass that wraps the methods at class-creation time is easy to write:
# requires Python 2.2 or later import types def tracing(f, name): def traced_f(*a, **k): print '%s(%s,%s) ->'%(name,a,k), result = f(*a, **k) print result return result return traced_f class meta_tracer(type): def _ _new_ _(self, classname, bases, classdict): for f in classdict: m = classdict[f] if isinstance(m, types.FunctionType): classdict[f] = tracing(m, '%s.%s'%(classname,f)) return type._ _new_ _(self, classname, bases, classdict) class tracer: _ _metaclass_ _ = meta_tracer
This recipe’s
tracing
function is nothing special—it’s just a
tracing wrapper closure that makes good use of the lexically nested
scopes supported in Python 2.2 (or 2.1 with from _ _future_ _ import nested_scopes
). We could use such a wrapper
explicitly in each class that needs to be traced. For example:
class prova: def a(self): print 'body: prova.a' a = tracing(a, 'prova.a') def b(self): print 'body: prova.b' b = tracing(b, 'prova.a')
This is okay, but it does require the explicit boilerplate insertion of the decoration (wrapper) around each method we want traced. Boilerplate is boring and therefore error-prone.
Custom
metaclasses let us perform such metaprogramming at class-definition
time without paying substantial overhead at each instance creation
or, worse, at each attribute access. The custom metaclass
meta_tracer
in this recipe, like most, inherits
from type
. In our metaclasses, we typically want
to tweak just one or a few aspects of behavior, not recode every
other aspect, so we delegate all that we don’t
explicitly override to type
, which is the common
metaclass of all built-in types and new-style classes in Python 2.2.
meta_tracer
overrides just one method, the special
method _ _new_ _
, which is used to create new
instances of the metaclass (i.e., new classes that have
meta_tracer
as their metaclass). _ _new_ _
receives as arguments the name of the new class, the
tuple of its bases, and the dict
produced by
executing the body of the class
statement. In
meta_tracer._ _new_ _
, we go through this
dictionary, ensuring that each function in it is wrapped by our
tracing
wrapper closure. We then call
type._ _new_ _
to do the rest.
That’s all! Every aspect of a class that uses
meta_tracer
as its metaclass is the same as if it
used type
instead, except that every method has
automagically been wrapped as desired. For example:
class prova(tracer): def a(self): print 'body: prova.a' def b(self): print 'body: prova.b'
This is the same as the prova
class of the
previous snippet, which explicitly wrapped each of its methods.
However, the wrapping is done automatically because this
prova
inherits from tracer
and
thus gets tracer
’s metaclass
(i.e., meta_tracer
). Instead of using class
inheritance, we could control metaclass assignment more explicitly by
placing the following statement in the class body:
_ _metaclass_ _ = meta_tracer
Or, more globally, we could place the following statement at the
start of the module (thus defining a module-wide global variable
named _ _metaclass_ _
, which in turn defines the
default metaclass for every class that doesn’t
inherit or explicitly set a metaclass):
_ _metaclass_ _ = meta_tracer
Each approach has its place in terms of explicitness (always a good trait) versus convenience (sometimes not to be sneered at).
Custom metaclasses also existed in Python Versions 2.1 and earlier,
but they were hard to use. (Guido’s essay
introducing them is titled “The Killer
Joke”, the implication being that those older
metaclasses could explode your mind if you thought too hard about
them!). Now they’re much simpler thanks to the
ability to subclass type
and do a few selective
overrides, and to the high regularity and uniformity of Python
2.2’s new object model. So there’s
no reason to be afraid of them anymore!
Currently, metaclasses are poorly documented; the most up-to-date documentation is in PEP 253 (http://www.python.org/peps/pep-0253.html).