The general syntax
The call to the built-in type() class can be used as a dynamic equivalent of the class statement. The following is an example of a class definition with the type() call:
def method(self): return 1 MyClass = type('MyClass', (object,), {'method': method})
This is equivalent to the explicit definition of the class with the class keyword:
class MyClass: def method(self): return 1
Every class that's created with the class statement implicitly uses type as its metaclass. This default behavior can be changed by providing the metaclass keyword argument to the class statement, as follows:
class ClassWithAMetaclass(metaclass=type): pass
The value that's provided as a metaclass argument is usually another class object, but it can be any other callable that accepts the same arguments as the type class and is expected to return another class object. The call signature is type(name, bases, namespace) and the meaning of the arguments are as follows:
- name: This is the name of the class that will be stored in the __name__ attribute
- bases: This is the list of parent classes that will become the __bases__ attribute and will be used to construct the MRO of a newly created class
- namespace: This is a namespace (mapping) with definitions for the class body that will become the __dict__ attribute
One way of thinking about metaclasses is the __new__() method, but at a higher level of class definition.
Despite the fact that functions that explicitly call type() can be used in place of metaclasses, the usual approach is to use a different class that inherits from type for this purpose. The common template for a metaclass is as follows:
class Metaclass(type): def __new__(mcs, name, bases, namespace): return super().__new__(mcs, name, bases, namespace) @classmethod def __prepare__(mcs, name, bases, **kwargs): return super().__prepare__(name, bases, **kwargs) def __init__(cls, name, bases, namespace, **kwargs): super().__init__(name, bases, namespace) def __call__(cls, *args, **kwargs): return super().__call__(*args, **kwargs)
The name, bases, and namespace arguments have the same meaning as in the type() call we explained earlier, but each of these four methods can have the following different purposes:
- __new__(mcs, name, bases, namespace): This is responsible for the actual creation of the class object in the same way as it does for ordinary classes. The first positional argument is a metaclass object. In the preceding example, it would simply be a Metaclass. Note that mcs is the popular naming convention for this argument.
- __prepare__(mcs, name, bases, **kwargs): This creates an empty namespace object. By default, it returns an empty dict, but it can be overridden to return any other mapping type. Note that it does not accept namespace as an argument because, before calling it, the namespace does not exist. Example usage of that method will be explained later in the New Python 3 syntax for metaclasses section.
- __init__(cls, name, bases, namespace, **kwargs): This is not seen popularly in metaclass implementations but has the same meaning as in ordinary classes. It can perform additional class object initialization once it is created with __new__(). The first positional argument is now named cls by convention to mark that this is already a created class object (metaclass instance) and not a metaclass object. When __init__() was called, the class was already constructed and so this method can do less things than the __new__() method. Implementing such a method is very similar to using class decorators, but the main difference is that __init__() will be called for every subclass, while class decorators are not called for subclasses.
- __call__(cls, *args, **kwargs): This is called when an instance of a metaclass is called. The instance of a metaclass is a class object (refer to Figure 1); it is invoked when you create new instances of a class. This can be used to override the default way of how class instances are created and initialized.
Each of the preceding methods can accept additional extra keyword arguments, all of which are represented by **kwargs. These arguments can be passed to the metaclass object using extra keyword arguments in the class definition in the form of the following code:
class Klass(metaclass=Metaclass, extra="value"): pass
This amount of information can be overwhelming at the beginning without proper examples, so let's trace the creation of metaclasses, classes, and instances with some print() calls:
class RevealingMeta(type): def __new__(mcs, name, bases, namespace, **kwargs): print(mcs, "__new__ called") return super().__new__(mcs, name, bases, namespace) @classmethod def __prepare__(mcs, name, bases, **kwargs): print(mcs, "__prepare__ called") return super().__prepare__(name, bases, **kwargs) def __init__(cls, name, bases, namespace, **kwargs): print(cls, "__init__ called") super().__init__(name, bases, namespace) def __call__(cls, *args, **kwargs): print(cls, "__call__ called") return super().__call__(*args, **kwargs)
Using RevealingMeta as a metaclass to create a new class definition will give the following output in the Python interactive session:
>>> class RevealingClass(metaclass=RevealingMeta): ... def __new__(cls): ... print(cls, "__new__ called") ... return super().__new__(cls) ... def __init__(self): ... print(self, "__init__ called") ... super().__init__() ... <class 'RevealingMeta'> __prepare__ called <class 'RevealingMeta'> __new__ called <class 'RevealingClass'> __init__ called >>> instance = RevealingClass() <class 'RevealingClass'> __call__ called <class 'RevealingClass'> __new__ called <RevealingClass object at 0x1032b9fd0> __init__ called
Let's take a look at the new Python 3 syntax for metaclasses.