Metaclasses

What is a Metaclass?

How Python normally creates class objects

In Python code you create a class with the class keyword:

>>> class Fred(object):
...     hair_color = 'brown'
...
>>> type(Fred)
<type 'type'>
>>> Fred.__name__
'Fred'
>>> Fred.hair_color
'brown'

Here is an equivalent to what Python did behind the scenes:

>>> Fred = type('Fred', (object,), {'hair_color':'brown'})
>>> type(Fred)
<type 'type'>
>>> Fred.__name__
'Fred'
>>> Fred.hair_color
'brown'

Declaring __metaclass__ allows you to customize class creation by overriding the default behavior of type().

Getting Started

Introducing the __metaclass__ magic variable

class __printer__(type):
    def __new__(meta, classname, bases, classDict):
        print 'Name            Type                 Value'
        print '-------------   ------------------   ---------------------------'
        for k, v in classDict.items():
            print '%-15s %-20s %r' % (k, type(v), v)

        return type.__new__(meta, classname, bases, classDict)

class Test(object):
    __metaclass__ = __printer__
    a = 47
    b = 'raspberry'
    def c(self): pass
    d = property(lambda self: None)

Expected output:

Name            Type                 Value
-------------   ------------------   ---------------------------
a               <type 'int'>         47
__module__      <type 'str'>         '__main__'
b               <type 'str'>         'raspberry'
__metaclass__   <type 'type'>        <class '__main__.__test__'>
d               <type 'property'>    <property object at 0x01A75850>
c               <type 'function'>    <function c at 0x01A17F30>

The __test__ metaclass prints out the members of its target classes. To make __test__ the metaclass of Test, we have to use the __metaclass__ magic variable.

All metaclasses inherit from class type. In most cases, you should override the __new__ magic method to modify the elements of the classDict argument. Modifying classDict has the effect of adding or modifying members of a class.

In most Python frameworks, metaclass usage is not explicit, because metaclasses are inherited by subclasses. This feature makes it easy to hide the metaclass machinery from your users. For example, Django's Model class uses a metaclass called ModelBase. When you create models in Django, you inherit your model classes from Model and never have to explicitly use the __metaclass__ attribute.

Distinguishing different types of class members

from inspect import isfunction, isdatadescriptor, isclass

def separate_members(classDict):
    d = dict(methods=[], properties=[], other=[])

    for k, v in classDict.items():
        if isfunction(v):
            d['methods'].append(k)
        elif isdatadescriptor(v):
            d['properties'].append(k)
        elif not isclass(v):
            d['other'].append(k)

    return d

Function separate_members shows you how to distinguish between methods, properties, and all other attributes from inside the metaclass. Note that methods defined in the target class appear as functions to the metaclass, hence we use isfunction instead of ismethod. Although we chose to use the inspect module for this example, it's possible to achieve equivalent functionality with the isinstance built-in function in conjunction with the types module.

Now to make use of the separate_members function we defined above:

class __printer__(type):
    def __new__(meta, classname, bases, classDict):
        d = separate_members(classDict)

        for k, v in d.items():
            print '%s:' % k
            for name in v:
                print '- %s' % name
            print '-'*80

        return type.__new__(meta, classname, bases, classDict)

class Test(object):
    __metaclass__ = __printer__
    a = 47
    b = 'raspberry'
    def c(self): pass
    d = property(lambda self: None)
    e = 45.67
    f = [23, 24, 25]
    g = lambda self: None
    def h(self, v): self.v = v
    i = property(c, h)

Expected output:

other:
- a
- __module__
- b
- f
- e
--------------------------------------------------------------------------------
properties:
- d
- i
--------------------------------------------------------------------------------
methods:
- g
- h
- c
--------------------------------------------------------------------------------

We make use of the separate_members function we defined in the previous section to allow metaclass __test__ to print out the methods, descriptors, and other attributes of its target class.

Use Cases

Generate attributes (Django)

from django.db import models

class Poll(models.Model):
    question = models.CharField(maxlength=200)
    pub_date = models.DateTimeField('date published')

class Choice(models.Model):
    poll = models.ForeignKey(Poll)
    choice = models.CharField(maxlength=200)
    votes = models.IntegerField()

This example was extracted from the official Django tutorial. The Model class uses a metaclass that generates additional attributes. For example, Poll objects have attributes choice_set and objects that were generated based on the declared class attributes.

Modify class attributes

class GoBot(Robot):
    head      = Appendage()
    left_arm  = Appendage()
    right_arm = Appendage()

    cpu            = Component()
    matrix         = Component()
    flux_capacitor = Component()

print GoBot.appendages
print GoBot.components

Expected output:

[Appendage<head>, Appendage<left_arm>, Appendate<right_arm>]
[Component<cpu>, Component<matrix>, Component<flux_capacitor>]

Class Robot makes use of a metaclass (not shown) that causes all objects of type Appendage and Component to be added to the class attributes appendages and components, respectively.

Transform methods and properties

class Transformer(Robot):
    __metaclass__ = __timer__

    def transform(self):
        "Transform into a GM vehicle of some kind"
        #...

    def attack(self, target):
        "Attack something with robotic fury"
        #...

t = Transformer()
t.transform()
t.attack(AdolfHitlerBot)

Expected output:

Took 3.715 seconds to transform!
Took 2.039 seconds to attack!

Class Transformer uses metaclass __timer__ (not shown), which transforms each of Transformer's methods to print out the execution duration after the completion of each method.

Recipes

Automatic Debugger Methods

This recipe shows you how you can focus your debugging on a specific class. In other words, have pdb run whenever any method of a class raises an exception.

class Example(object):
    __metaclass__ = __debugger__

    def divide(self, v):
        # exception if v == 0
        result = 100 / v
        return result

    def interpolate(self, *args):
        # exception if len(args) != 2
        result = "%s is %s!" % args
        return result

e = Example()
print e.divide(10)
print e.divide(0)       # enters debugger

In this recipe, we'll explore how to transform all the methods of a class so that you automatically step into the debugger if and only if a method invocation triggers an error.

Generating Bitfield Properties

We've all had to deal with libraries that use bitfields. It's especially common when dealing with wrapper libraries for C/C++ libraries. For this example, assume that we have a Widget class that has a bitfield attribute style. After using this class for a while, you've gotten tired of having to write code like this:

w = Widget()
w.style = ENABLED | SUNKEN | TRANSPARENT

Instead, you wish you could just write code like this:

w = Widget()
w.enabled = True
w.sunken = True
w.transparent = True

The obvious way to do this would be to modify Widget to have a bunch of properties that modify the style attribute:

class Widget(object):
    style = 0

    enabled = property(
        lambda self: bool(self.style & ENABLED),
        lambda self, v: setattr(self, 'style',
            self.style | ENABLED if v else self.style ^ ENABLED),
    )
    simple = property(
        lambda self: bool(self.style & SIMPLE),
        lambda self, v: setattr(self, 'style',
            self.style | SIMPLE if v else self.style ^ SIMPLE),
    )
    sunken = property(
        lambda self: bool(self.style & SUNKEN),
        lambda self, v: setattr(self, 'style',
            self.style | SUNKEN if v else self.style ^ SUNKEN),
    )
    raised = property(
        lambda self: bool(self.style & RAISED),
        lambda self, v: setattr(self, 'style',
            self.style | RAISED if v else self.style ^ RAISED),
    )
    transparent = property(
        lambda self: bool(self.style & TRANSPARENT),
        lambda self, v: setattr(self, 'style',
            self.style | TRANSPARENT if v else self.style ^ TRANSPARENT),
    )

If you're thinking "Gosh, it probably wasn't fun to type all that", you'd be totally right. Luckily for us, ever since Python 2.2 we've had a really good feature called metaclasses, which were created just for this kind of situation. Using a custom metaclass, we can rewrite the code like this:

class Widget(object):
    __metaclass__ = __bitproperties__
    style = 0
    bit_properties = ['enabled', 'simple', 'sunken', 'raised', 'transparent']

Here's another possible way to rewrite it, still using metaclasses:

class Widget(object):
    __metaclass__ = __bitproperties__
    style = 0

    enabled       = bit_property(ENABLED)
    simple_border = bit_property(SIMPLE)
    sunken_border = bit_property(SUNKEN)
    raised_border = bit_property(RAISED)
    transparent_background = bit_property(TRANSPARENT)

Metaclass Caveats

Not as easy to use as frame hacks

Sometimes what you want to do can be achieved using either a metaclass or a frame hack. How do you choose which to use? When prototyping, always go with the frame hack since it requires less effort. However, if you need your production code to be highly portable, use metaclasses. How you want your API to look is another consideration. Frame hacks add a different kind of structure to class definitions, and that may make your API look more elegant from a user's perspective.

Might generate extra classes

The __new__ method of a metaclass returns a new type object. Because of the way that Python's import mechanism works, this may cause more than one class to be generated. In general, this shouldn't be a problem. But it may be something you want to keep in mind.

Makes a mess of your code

Excessive use of metaclasses can make your code harder to read and maintain. If you can achieve equivalent results using simpler approaches such as inheritance or frame hacks, use them instead.

Exercises

Go Back