Credit: Luther Blissett
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
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.
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
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
_ _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
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
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
_ _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).