Cleverdevil Python Metaclasses Demystified
Cleverdevil Python Metaclasses Demystified
about
photos
computing
contact
syndicate
training
Revisiting Classes
Most Python programmers are familiar with creating classes, and might even create or modify their own classes several
times a week in the course of their work. Many of us were even taught about classes and object-oriented programming
in the course of our education. But, when was the last time any of us really thought about classes in depth since we
were first introduced to the concept? Id wager that most of us take classes for granted, and dont really think about
what they are providing for us, and more importantly, how they provide it to us.
Understanding metaclasses can be greatly simplified by taking another look at Python classes, from a slightly different
perspective. In order to do this, lets pose a simple question. What exactly does a class do?
My first instinct when attempting to answer this question is to rely on my education, which unearths big, fancy,
computer-sciency words like encapsulation and abstraction. Its tempting to allow this to cloud our thinking, so lets
do our best to think at a much simpler level when considering this question. So, I ask again what does a class do?
Fundamentally, a class is used to construct objects called instances. This is essentially all a class does creates instances.
The process of creating instances using a class is called instantiation. For example, given a class Person, we can
instantiate it to create an instance of that Personclass.
class Person(object):
pass
jonathan = Person()
This instance is related back to its class in that the class is what constructs the instance. What do we mean by
construct? Well, when a class is instantiated, it constructs an instance by providing it with its namespace. We all
know from Tim Peters The Zen of Python that namespaces are a honking great idea, and classes are a great
example of why! The attributes in the namespace of a class are used to define the namespace of its instances, thus
providing those instances with behavior and state. Lets enhance our Personclass, to see how this works.
>>> class Person(object):
...
greeting = 'Hello'
...
...
def greet(self, who):
...
print self.greeting, who
>>> jonathan = Person()
>>> jonathan.greet('Readers')
'Hello Readers'
The Personclass now has two attributes in its namespace greetingand greet. When we instantiate our
Personclass, our instance is given both state, in the form of the greetingattribute, and behavior, in the form of the
greetmethod. In this way, a class is of critical and primary importance in defining how its instances will behave.
So, what does a class do? Lets summarize. In a nutshell, a class is an object with constructs instances through a process
called instantiation, therefore defining the namespace, behavior, and state of that instance.
Defining Metaclasses
Now that weve established clearly what a class is, we can confidently pose the question what exactly is a metaclass?
When sifting through documentation, the most common answer youll find to that question is short, but often difficult to
unpack:
A metaclass is the class of a class.
While this answer is certainly correct and concise, and tells us what a metaclass is, it still doesnt tell us what a metaclass
does. Lets look at an alternate answer to the question:
A metaclass is a class whose instances are classes.
I will concede that this answer is just as much of a mouthful as its counterpart, but it has the benefit of giving us a clue
as to what a metaclass does! In short, a metaclass constructs classes. We also know that a metaclass is just another class,
and weve just spent some time outlining what a class does. As a result, we should be able to build upon what weve
learned about classes earlier to determine a bit more about metaclasses.
Recall that a class helps define the behavior and state of its instances. Metaclasses provide the same basic capability to
classes, giving you the ability to change the way a class will behave at runtime. This technique is commonly referred to
as metaprogramming.
Having a definition of metaclasses and metaprogramming is useful, but lets take a deeper look by investigating an existing
metaclass.
The typeMetaclass
In Python, classes which inherit from objectare called new-style classes. All such new-style classes have a default
metaclass called type. We can prove that typeis the metaclass of objectthrough simple introspection in the
Python interpreter. Recall, a metaclass is the class of a class, so what is the class of object:
>>> print object.__class__
<type 'type'>
Just as we expected! The metaclass of objectis type, and thus all classes which inherit from objectwill be
provided with this metaclass by default. Classes that do not inherit from objectare called old-style classes and will
disappear in Python 3.0. While old-style classes also support metaclasses and metaprogramming, well focus on newstyle classes in this article for the sake of simplicity.
Typically, classes are defined using the classstatement in Python, as we saw in our earlier Personexample.
However, we have just learned that metaclasses create classes when they are instantiated. This means that we should be
able to define a class by instantiating the typemetaclass manually.
Lets define our original Personclass again, but this time, lets do it without using the classstatement by instantiating
the typemetaclass:
>>> def greet(self, who):
...
print self.greeting, who
>>> Person = type(
...
'Person',
...
(object,),
...
{'greet': greet, 'greeting': 'Hello'}
... )
>>>
>>> jonathan = Person()
>>> jonathan.greet('Readers')
'Hello Readers'
This method of creating classes is equivalent to using the classstatement, and reveals quite a bit about how
metaclasses work. The constructor for a metaclass expects very specific arguments:
1. The first argument is the name of the class.
2. The second argument is a tuple of the base classes for the class.
3. The last argument is a dictionary representing the namespace of the class. This dictionary contains all of the
attributes that would typically appear within the body of a class statement.
Now, weve seen a metaclass in action, and we know how to instantiate them to create classes. Armed with this
knowledge of the default typemetaclass, we can now tackle the much more interesting problem of creating our own
metaclasses.
Defining Metaclasses
Defining metaclasses in Python is as simple as creating a class that inherits from the built-in typemetaclass. The
constructor for our metaclass will take the same arguments as the constructor for the typemetaclass:
1. The class name.
2. A tuple of the class bases.
3. A dictionary representing the namespace of the class.
Constructors typically perform some action on their instances, so lets make our constructor set a flag on the instance
that we can inspect to make sure that our metaclass is being used.
class MyFirstMeta(type):
def __init__(cls, name, bases, ns):
cls.uses_my_metaclass = True
One important thing to note here is that the first argument to the constructor of a class is typically called self, as it
refers to the instance being constructed. It is conventional to name the first argument of a metaclass constructor clsas
the metaclass instance is actually a class.
Now that weve defined our metaclass, we know that we can construct a new class called MyClasssimply by
instantiating the metaclass:
>>> MyClass = MyFirstMeta('MyClass', (object,), {})
>>> print MyClass.uses_my_metaclass
True
This very simple metaclass has given us our first glimpse into the power of metaclasses. Within our metaclass
constructor, we gave our class some state in the form of a boolean attribute uses_my_metaclass. Metaclasses have
the power to add, remove, or modify any attribute of the class being constructed. Metaclasses will frequently add or
replace methods on their instances, based upon the data in the namespace of the class. Many Python object-relational
mappers use metaclasses to transform the attributes of a class into database table definitions, for example.
While you can certainly construct classes by manually instantiating custom metaclasses, it is much more convenient to
use the classstatment to create your classes. Python allows you to define the metaclass for a class by using the special
__metaclass__attribute in your class statement:
class MyClass(object):
__metaclass__ = MyFirstMeta
This is the preferred method of attaching metaclasses to your classes. An important thing to note about this syntax is
that while you are not manually instantiating the metaclass when defining your classes this way, the Python interpeter will
instantiate the metaclass. The metaclass instantiation will occur immediately after the class statement has been fully
executed. As a result, bugs in metaclasses often are triggered during imports. In a way, the class statement is simply
syntactic sugar for instantiating metaclasses!
to find Fielddefinitions:
class EnforcerMeta(type):
def __init__(cls, name, bases, ns):
# store the field definitions on the class as a dictionary
# mapping the field name to the Field instance.
cls._fields = {}
# loop through the namespace looking for Field instances
for key, value in ns.items():
if isinstance(value, Field):
cls._fields[key] = value
Our metaclass first attaches a _fieldsdictionary to the class itself. This data structure is where we will store Field
definitions for later use. We then loop through the items in the namespace looking for Fieldinstances, and finally we
store them in our _fieldsdictionary.
Next up is the Enforcerbase class itself. The Enforcerbase class first needs to attach the EnforcerMeta
metaclass weve just defined. This is a very common way for libraries to distribute their metaclasses, by defining a base
class to inherit from, rather than requiring users to even know that a metaclass is being used, or how to attach the
metaclass to their classes.
The second thing the Enforcerbase class needs to do is to override the __setattr__method. This is a special
method on Python classes that allows you to override the default attribute setting behavior on your Python objects. The
__setattr__method takes in the name of the attribute being set, and the value being set.
class Enforcer(object):
# attach the metaclass
__metaclass__ = EnforcerMeta
def __setattr__(self, key, value):
if key in self._fields:
if not self._fields[key].is_valid(value):
raise TypeError('Invalid type for field!')
super(Enforcer, self).__setattr__(key, value)
Our Enforcerclass first attaches the metaclass. Then, it overrides the __setattr__method so that it can watch
for field assignments. First, we check to see if the attribute being set is one of our defined fields. Then, we ask the field
definition if the value that is being passed is valid for the field definition. If it is not a valid type for the field, we raise a
TypeError.
The last line of the Enforcerclass is extremely important. This line is instructing the Python interpreter to call the
__setattr__implementation on the appropriate superclass definition, in this case object. Without this line,
attribute setting would fail on all Enforcersubclasses.
Lets try out our new creation from the Python interactive prompt:
>>> class Person(Enforcer):
...
name = Field(str)
...
age = Field(int)
...
>>> jonathan = Person()
>>> jonathan.name = 3
TypeError: Invalid type
>>> jonathan.age = 'Old'
TypeError: Invalid type
>>> jonathan.name = 'Jonathan'
>>> jonathan.age = 28
Our metaclass has completely changed the way that our class behaves, transforming it into a more rigid class definition
that keeps our Java-loving friends happy. While such restrictive enforcement is atypical in Python, it certainly shows how
powerful metaclasses can be.
This example also illustrates a common pattern for metaclasses, in which a class describes how it wants to behave in a
declarative way without actually writing any code to implement that behavior. The metaclass subsequently takes that
metadata, and uses it to reprogram the class. This is the essence of what metaprogramming brings to the table, and is
the most popular use-case for metaclasses.
Cautionary Notes
Metaclasses are fantastically cool, and as weve seen, they can be pretty useful. The rise of object-relational mappers and
web frameworks have put metaclasses into use by an increasing number of Python users, and has increased the visibility
of metaclasses substantially. That being said, metaclasses are a feature of Python that must be used carefully. Because
metaclasses do their work at class definition time, bugs in your metaclasses can result in errors that are triggered at
import time. In addition, metaclasses are often hidden from the programmer behind a base class, which can cause
confusion.
Applied judiciously, metaclasses can be a great tool for solving problems, and give Python programmers the ability to
take advantage of metaprogramming techniques in their own code. I hope that this article has shed some light on what
metaclasses are, and has taken some of the mystery out of metaclasses.
Comment
1. Great article. I dont think I really understood meta classes until now. I also liked that you included a note on
how you could shoot yourself in the foot. Every cool feature has its draw backs, but most people dont include
these in their tutorials.
-Mark
Mark Roddy
2. I frequently use metaclasses to keep track of a collection of subclasses so that I creating a new subclass of my
class can (often extend) existing behavior declaratively.
class LeafClassesMeta(type):
"""
A metaclass for classes that keeps track of all of them that
aren't base classes.
"""
_all_classes = set()
def __init__(cls, name, bases, attrs):
cls._all_classes.add(cls)
# remove any base classes
cls._all_classes -= set(bases)
Jason R. Coombs
3. Ive read you examples but I have one question. It seems to me that in the examples you gave I could use
subclass to achieve the same result. For example create a class AutoDecoratorMeta and then use that to create
my class Person(AutoDecoratorMeta):
Would there be a some easy piratical case where metaclass would work but subclass would not.
Thanks,
Lucas
Lucas
4. Thanks for the tutorial! This is by far the best descriptions of what metaclasses are, and how theyre used.
I agree with Lucas, though, that Im not clear on exactly WHY Id want to use them yet.
Fitzgerald Steele
5. Jonathan, thanks for this post! I went to your talk at pyatl. It was a great talk: easy to follow, entertaining, and it
was my first exposure to meta classes in Python. I have made use of meta classes several times since then.
Toby Ho