Previous Page
Next Page

5.4. Metaclasses

Any object, even a class object, has a type. In Python, types and classes are also first-class objects. The type of a class object is also known as the class's metaclass.[*] An object's behavior is mostly determined by the type of the object. This also holds for classes: a class's behavior is mostly determined by the class's metaclass. Metaclasses are an advanced subject, and you may want to skip the rest of this section on first reading. However, fully grasping metaclasses can help you obtain a deeper understanding of Python, and occasionally it can be useful to define your own custom metaclasses.

[*] Strictly speaking, the type of a class C could be said to be the metaclass only of instances of C rather than of C itself, but this exceedingly subtle terminological distinction is rarely, if ever, observed in practice.

The distinction between legacy and new-style classes relies on the fact that each class's behavior is determined by its metaclass. In other words, the reason legacy classes behave differently from new-style classes is that legacy and new-style classes are objects of different types (metaclasses):

class Classic: pass class Newstyle(object): pass print type(Classic)                  # prints: <type 'class'>
print type(Newstyle)                 # prints: <type 'type'>

The type of Classic is object types.ClassType from standard module types, while the type of Newstyle is built-in object type. type is also the metaclass of all Python built-in types, including itself (i.e., print type(type) also prints <type 'type'>).

5.4.1. How Python Determines a Class's Metaclass

To execute a class statement, Python first collects the base classes into a tuple t (an empty one if there are no base classes) and executes the class body in a temporary dictionary d. Then, Python determines the metaclass M to use for the new class object C that the class statement is creating.

When '_ _metaclass_ _' is a key in d, M is d['_ _metaclass_ _']. Thus, you can explicitly control class C's metaclass by binding the attribute _ _metaclass_ _ in C's class body. Otherwise, when t is nonempty (i.e., when C has one or more base classes), M is the leafmost metaclass among all of the metaclasses of C's bases.[*] This is why inheriting from object indicates that C is a new-style class. Since type(object) is type, a class C that inherits from object (or some other built-in type) gets the same metaclass as object (i.e., type(C), C's metaclass, is also type). Thus, being a new-style class is synonymous with having type as the metaclass.

[*] if C's bases' metaclasses do not form an inheritance lattice including its lower boundi.e., if there is no leafmost metaclassPython raises an exception diagnosing this metatype conflict.

When C has no base classes, but the current module has a global variable _ _metaclass_ _, M is the value of that global variable. This lets you make classes without base classes default to new-style classes, rather than legacy classes, throughout a module. Just place the following statement toward the start of the module body:

_ _metaclass_ _ = type

Failing all of these, M defaults to types.ClassType. This last "default of defaults" clause is why classes without base classes are legacy by default, when _ _metaclass_ _ is not bound in the class body or as a global variable of the module.

5.4.2. How a Metaclass Creates a Class

Having determined M, Python calls M with three arguments: the class name (a string), the tuple of base classes t, and the dictionary d. The call returns the class object C, which Python then binds to the class name, completing the execution of the class statement. Note that this is in fact an instantiation of type M, so the call to M executes M._ _init_ _(C, namestring, t, d), where C is the return value of M._ _new_ _(M, namestring, t, d), just as in any other similar instantiation of a new-style class.

After class object C is created, the relationship between class C and its type (type(C), normally M) is the same as that between any object and its type. For example, when you call the class object C (to create an instance of C), M._ _call_ _ executes with class object C as the first actual argument.

Note the benefit of the new-style approach described in "Per-Instance Methods" on page 103. Calling C to instantiate it must execute the metaclass's M._ _call_ _, whether or not C has a per-instance attribute (method) _ _call_ _ (i.e., independently of whether instances of C are or aren't callable). This requirement is simply incompatible with the legacy object model, where per-instance methods override per-class oneseven for implicitly called special methods. The new-style approach avoids having to make the relationship between a class and its metaclass an ad hoc special case. Avoiding ad hoc special cases is a key to Python's power: Python has few, simple, general rules, and applies them consistently. Defining and using your own metaclasses

It's easy to define custom metaclasses: inherit from type and override some of its methods. You can also perform most of these tasks with _ _new_ _, _ _init_ _, _ _getattribute_ _, and so on without involving metaclasses. However, a custom metaclass can be faster, since special processing is done only at class creation time, which is a rare operation. A custom metaclass lets you define a whole category of classes in a framework that magically acquires whatever interesting behavior you've coded, quite independently of what special methods the classes may choose to define. Moreover, some behavior of class objects can be customized only in metaclasses. The following example shows how to use a metaclass to change the string format of class objects:

class MyMeta(type):
    def _ _str_ _(cls): return "Beautiful class '%s'"%cls._ _name_ _
class MyClass:
    _ _metaclass_ _ = MyMeta x = MyClass( )
print type(x)       # emits: Beautiful class 'MyClass'

Strictly speaking, classes that instantiate such a custom metaclass are neither classic nor new-style: the semantics of classes and of their instances are entirely defined by their metaclass. In practice, custom metaclasses almost invariably subclass built-in type. Therefore, the semantics of the classes that instantiate such custom metaclasses are best thought of as variations on the semantics of new-style classes. A substantial custom metaclass example

Suppose that, programming in Python, we miss C's struct type: an object that is just a bunch of data attributes with fixed names. Python lets us easily define an appropriate generic Bunch class, apart from the fixed names:

class Bunch(object):
    def _ _init_ _(self, **fields): self._ _dict_ _ = fields p = Bunch(x=2.3, y=4.5)
print p                     # prints: <_ _main_ _.Bunch object at 0x00AE8B10>

However, a custom metaclass lets us exploit the fact that the attribute names are fixed at class creation time. The code shown in Example 5-1 defines a metaclass, metaMetaBunch, and a class, MetaBunch, that let us write code like the following:

class Point(MetaBunch):
    """ A point has x and y coordinates, defaulting to 0.0, and a color,
        defaulting to 'gray' -- and nothing more, except what Python and
        the metaclass conspire to add, such as _ _init_ _ and _ _repr_ _
    x = 0.0
    y = 0.0
    color = 'gray'
# example uses of class Point q = Point( )
print q                     # prints: Point( )
p = Point(x=1.2, y=3.4)
print p                     # prints: Point(y=3.399999999, x=1.2)

In this code, the print statements print readable string representations of our Point instances. Point instances are quite memory-lean, and their performance is basically the same as for instances of the simple class Bunch in the previous example (there is no extra overhead due to special methods getting called implicitly). Note that Example 5-1 is quite substantial, and following all its details requires understanding aspects of Python covered later in this book, such as strings (Chapter 9) and module warnings ("The warnings Module" on page 471). The identifier mcl used in Example 5-1 stands for "metaclass," a use that is clearer in this special advanced case than the more habitual case of cls standing for "class."

Example 5-1. The metaMetaBunch metaclass

import warnings class metaMetaBunch(type):
    metaclass for new and improved "Bunch": implicitly defines _ _slots_ _,
   _ _init_ _ and _ _repr_ _ from variables bound in class scope.
    A class statement for an instance of metaMetaBunch (i.e., for a class
    whose metaclass is metaMetaBunch) must define only class-scope data
    attributes (and possibly special methods, but NOT _ _init_ _ and
    _ _repr_ _!).  metaMetaBunch removes the data attributes from class
    scope, snuggles them instead as items in a class-scope dict named
    _ _dflts_ _, and puts in the class a _ _slots_ _ with those attributes'
    names, an _ _init_ _ that takes as optional keyword arguments each of
    them (using the values in _ _dflts_ _ as defaults for missing ones), and
    a _ _repr_ _ that shows the repr of each attribute that differs from its
    default value (the output of _ _repr_ _ can be passed to _ _eval_ _ to
    make an equal instance, as per the usual convention in the matter, if
    each of the non-default-valued attributes respects the convention too)
    def _ _new_ _(mcl, classname, bases, classdict):
        """ Everything needs to be done in _ _new_ _, since type._ _new_ _ is
            where _ _slots_ _ are taken into account.
        # define as local functions the _ _init_ _ and _ _repr_ _ that we'll
        # use in the new class
        def _ _init_ _(self, **kw):
            """ Simplistic _ _init_ _: first set all attributes to default
                values, then override those explicitly passed in kw.
            for k in self._ _dflts_ _: setattr(self, k, self._ _dflts_ _[k])
            for k in kw: setattr(self, k, kw[k])
        def _ _repr_ _(self):
            """ Clever _ _repr_ _: show only attributes that differ from the
                respective default values, for compactness.
            rep = ['%s=%r' % (k, getattr(self, k)) for k in self._ _dflts_ _
                    if getattr(self, k) != self._ _dflts_ _[k]
            return '%s(%s)' % (classname, ', '.join(rep))
        # build the newdict that we'll use as class-dict for the new class
        newdict = { '_ _slots_ _':[  ], '_ _dflts_ _':{  },
            '_ _init_ _':_ _init_ _, '_ _repr_ _':_ _repr_ _, }
        for k in classdict:
            if k.startswith('_ _') and k.endswith('_ _'):
                # special methods: copy to newdict, warn about conflicts
                if k in newdict:
                    warnings.warn("Can't set attr %r in bunch-class %r"
                        % (k, classname))
                    newdict[k] = classdict[k]
                # class variables, store name in _ _slots_ _, and name and
                # value as an item in _ _dflts_ _
                newdict['_ _slots_ _'].append(k)
                newdict['_ _dflts_ _'][k] = classdict[k]
        # finally delegate the rest of the work to type._ _new_ _
        return super(metaMetaBunch, mcl)._ _new_ _(
                     mcl, classname, bases, newdict)
class MetaBunch(object):
    """ For convenience: inheriting from MetaBunch can be used to get
        the new metaclass (same as defining _ _metaclass_ _ yourself).
    _ _metaclass_ _ = metaMetaBunch

Previous Page
Next Page