This writeup discusses particularly metaclasses in the
Python programming language. They can be quite different in other languages.
Suppose you have a class; let's say it's called Book.
And you know what you can do with that class object? You can call it.
And every time you do so, you get an instance of the class — you
get a book.
b1 = Book('Of Mice and Men')
b2 = Book('Just So Stories')
You could keep calling Book all day, and each time get a Book instance
to add to your library. The instances are different because of the
different parameters you pass when you call the class.
For ease of illustration, let's see a minimal Book class.
class Book(object):
def __init__(self, title):
self.title = title
def bind(self, method):
pass
Now, let's consider a different class. Different, but same.
It's called, and it produces instances. But these instances happen to be
classes — this class is a metaclass. Hmmm … what's
the point of that? And what would you pass
in the call to differentiate the various classes produced, one from the
other?
Let's put on our thinking caps and try to figure out,
What Would Guido Do? Well, in the normal case, when
you call the class, its initializing method (__init__) is
called, receiving a new blank instance of the class, and the parameters
that were passed to the call. Our metaclass is a class, so it's going to
have to work the same way (almost). Its __init__ method will be called
and it will receive a new instance (which in this case is a class, because
the instances of a metaclass are classes), and just as the normal class
initializes the instance, so will the metaclass (almost).
And what parameters will __init__ receive? I don't even recall calling
the metaclass, so I don't think I gave it the parameters. There has to
be something special about these metaclasses, or C-Dawg wouldn't be
writing this. Special in that Python itself makes the call, rather than
the programmer. So this is where the slight differences, between
"normal" classes and metaclasses, come into play. We're going to say
that the metaclass is called as the last step of executing a
class statement, and Python passes to it the new
class being constructed and various kinds of information about it. Since
Python is making the call behind the scenes, we will have no influence
on what the call looks like. Guido, pull back the curtain
and show us!
class M(object):
def __init__(icls, classname, bases, namespace):
pass
Okay, __init__ receives the class, the name of the class, a tuple of
base classes that the class will inherit from, and a dictionary containing
the attributes and methods that we defined. From this we surmise that what
the statement class Book(object): does is, saves the tuple
of base classes, creates an empty dictionary and populates it with the
functions and data objects defined within the scope of the class statement,
calls the metaclass passing it those items, and binds the name ("Book") to
the object that it returns.
So what? The class is already defined by the time the metaclass sees
it (unlike a normal instance, which is empty when the class receives it for
initialization); I've already done all the hard work, so what good is the
metaclass?
The metaclass can take your already-working class and shape it, polish it,
refine it. It can make a variant of it, or it can complete it using what
you gave it as boilerplate. But it all happens at the time your class
is defined. The metaclass can add or delete attributes or methods, or
alter what is there; it cannot alter the base class list, however.
A class defines its metaclass by binding it to a class attribute called
__metaclass__. Purists might complain that it should
be defined by a syntactical feature, but this way is Pythonic and
backward-compatible.
Keep in mind that the metaclass never gets its hands on your
class's instances, and especially that your class does not
inherit from its metaclass. Also, unlike with inheritance,
a class can only have one metaclass. However, since a metaclass is a
class, it can inherit from one or more classes. Thus, if you want to
use the services of two metaclasses, say MC1 and MC2, all you need to do is
define MC3 that inherits from them, and make that your metaclass.
And lastly, metaclasses are rarely used for normal application purposes,
the default ones that Python supplies being appropriate.
Nitty gritty points that wouldn't be obvious:
- Your metaclass must subclass type
- If the metaclass wants to add references (variables or functions) to the class, it must do so using the setattr function. Adding entries to the ns dictionary will not have that effect. ns should be treated as read only.
- If the class defined __slots__, the metaclass is unable to modify the slots.
I'm going to present an example of a metaclass. It modifies a class
so that every entry to and exit from any of its methods is logged.
How? For each method that I define in my class, it writes a new function
that logs the call, calls my method, logs the return value, and returns
that returned value to the caller. The important thing to realize is
that this whole process can be turned on and off simply by declaring
the class to use the metaclass or not. (And, as seen in the example, the builtin variable __debug__ can be used to control that.)
class MC(type):
"This metaclass causes all methods of a class to be traced."
def __init__(cls, clsname, bases, ns):
def addtrace(f):
"""Returns a function which will trace calls to the
given function.
"""
def t(self, *args, **kwargs):
self.writelog('Enter %s(%s, %s)\n' % (f.__name__, args, kwargs))
ret = f(self, *args, **kwargs)
self.writelog('Return %s\n' % `ret`)
return ret
return t
from types import FunctionType
for name,obj in ns.items():
if type(obj) == FunctionType:
setattr(cls, name, addtrace(ns[name]))
class Logger(object):
def writelog(self, s):
print s
class C(Logger):
if __debug__:
__metaclass__ = MC
def add(self, a, b):
return a+b
And here is a sample of it in action:
>>> i = C()
>>> sum = i.add(45, 9)
Enter add((45, 9), {})
Return 54
>>> print sum
54