This is a quick tutorial over the basics of what metaclasses do.
The Metaclass
Metaclasses, while seemingly a complex topic, really just do something very simple. They control what happens when you have code that turns into a class object. The normal place they are executed is right after the class statement. Let’s see that in action by using print as our metaclass.
Note: this post uses Python 3 metaclass notation. Python 2 uses assignment to a special
__metaclass__
attribute to set the metaclass. Also, Python 2 requires explicit, 2 argumentsuper()
calls.
class WillNotBeAClass(object, metaclass=print):
x = 1
Here, we have replaced the metaclass (type
) with print
, just to investigate
how it works. This is quite useless, of course, but does show that the metaclass
gets called with three arguments when a class is created. The first is the name
of the class to be created, the second is a tuple of base classes, and the third
is a dictionary that has the namespace of the body of a class, with a few extra
special values added.
Given this, we know see show to make this into a class using type: (I will not
bother to add __module__
and __qualname__
for now, they are not needed)
class WillBeAClass(object):
x = 1
WillBeAClass
WillAlsoBeAClass = type("WillAlsoBeAClass", (object,), {"x": 1})
WillAlsoBeAClass
These two objects, WillBeAClass
and WillAlsoBeAClass
, are basically the same
thing. The second method is exactly what the class statement does (with
__module__
and __qualname__
added).
The type
So, we are done with metaclasses, that’s all there is to know. However, to
actually make useful classes, you probably want to make normal classes, just
with some sort of modification. For that, you need to understand type
, and how
it works, and how to subclass it.
First, let’s pretend we can just patch type and ignore subclassing. You probably already see how to do that:
def newtype(*args):
print("I'm sort of a new type, but I have a problem!")
return type(*args)
class NewClass(object, metaclass=newtype):
x = 1
class NewNewClass(NewClass):
y = 2
All was fine and well, until we subclassed NewClass
. The metaclass did not
come along for the ride! That’s because type
adds a reference to itself when
it creates a class:
NewClass.__class__
Note: the standard way to check the class of an object is to call
type(NewClass)
, however, since that is an unrelated use oftype
that is there for historical reasons, I’ve avoided using it here)
How type works
We must subclass type to get a metaclass that actually works on subclasses, too: (Here I’m overriding all the used parameters, so that you can see where each gets called)
class NewType(type):
def __new__(cls, *args, **kargs):
print("I'm a new type! __new__")
return super().__new__(cls, *args, **kargs)
def __init__(self, *args, **kargs):
print("I'm a new type! __init__")
super().__init__(*args, **kargs)
@classmethod
def __prepare__(cls, *args, **kargs):
print("I'm new in Python 3! __prepare__")
return super().__prepare__(cls, *args, **kargs)
def __call__(self, *args, **kargs):
print("I'm a new type! __call__")
return super().__call__(*args, **kargs)
class NewClass(object, metaclass=NewType):
def __init__(self):
print("I'm init in the class")
def __new__(cls):
print("I'm new")
return super().__new__(cls)
class NewNewClass(NewClass):
y = 2
instance = NewClass()
Notice how __init__
was used, too? This gives us a peek at one more feature of
metaclasses: the __class__
parameter of a class is used to create instances.
The super part of __call__
actually puts together the class, it’s where
__new__
and __init__
are called, etc.
Python 3 only
As you already have seen, the __prepare__
method is only in Python 3, and
allows you to customize the __dict__
before __new__
, however, as a reminder,
in CPython the dict for a class is written in C and is not customizable (ie,
can’t be ordered, etc). So you’ll have to manage that yourself, but
__prepare__
helps. It returns a dictionary-like object that then collects the
namespace, then gets passed to __new__
.
from collections import OrderedDict
class PrepareMeta(type):
def __new__(cls, name, bases, ns):
print(ns)
return super().__new__(cls, name, bases, ns)
@classmethod
def __prepare__(cls, *args, **kargs):
return OrderedDict()
class PrepareClass(metaclass=PrepareMeta):
y = 2
We only get that one look at the dict, since it becomes the special C
mappingproxy
once the class is created.
PrepareClass.__dict__
Another Python 3 only feature is class level arguments. You can do things like this:
class ArgMeta(type):
def __new__(cls, *args, **kargs):
print(kargs)
return super().__new__(cls, *args)
def __init__(self, *args, **kargs):
return super().__init__(*args)
class ArgClass(metaclass=ArgMeta, kwarg=2):
y = 2
Example
A dictionary can be make using the following ugly hack:
class a_dictionary(
metaclass=lambda name, bases, ns: {n: ns[n] for n in ns if "__" not in n}
):
one = 1
two = 2
three = 3
print(a_dictionary)
An ordered class can be make using the __prepare__
method (Python 3 only):
class OrderedMeta(type):
def __new__(cls, name, bases, ns):
ns["orderednames"] = list(ns)
return super().__new__(cls, name, bases, ns)
@classmethod
def __prepare__(cls, *args, **kargs):
return OrderedDict()
class Ordered(metaclass=OrderedMeta):
one = 1
two = 2
three = 3
four = 4
five = 5
print(Ordered.__dict__)
Here we can see that the list orderednames
is ordered correctly.