Content

Quick Recap

How do metaclasses work?

To understand how Python metaclasses work, you need to be very comfortable with the notion of types in Python.

>>> name = "andy"
>>> print(type(name))
<class 'str'>
>>> print(type(str))
<class 'type'>
>>> print(type(type))
<class 'type'>

Remember: every type in Python is defined by a class.

>>> class Person:
    pass
>>> andy = Person()
>>> print(type(andy))
<class '__main__.Person'>

A class is just another object and can be modified:

>>> andy.dob = "2/27/1960"
>>> print(andy.dob)
2/27/1960

Hence, Metaclasses modifying classes.

Creating custom Metaclass

In Python you can assign a metaclass to the creation of a new class by passing in the intended masterclass to the new class definition.

The type type, as the default metaclass in Python, defines special methods that new metaclasses can override to implement unique code generation behavior. Here is a brief overview of these “magic” methods that exist on metaclass:

  • `__new__`: This method is called on the Metaclass before an instance of a class based on the metaclass is created

  • `__init__`: This method is called to set up values after the instance/object is created

  • `__prepare__`: Defines the class namespace in a mapping that stores the attributes

  • `__call__`: This method is called when the constructor of the new class is to be used to create an object

These are the methods to override in your custom metaclass to give your classes behavior different from that of type, which is the default metaclass.

To create our custom metaclass, it must inherit type and will often override: * `__new__()`: It’s a method which is called before `__init__()`. It creates the object and return it. We can override this method to control how the objects are created. * `__init__()`: This method initializes the created object passed as parameter

# our metaclass
class MultiBases(type):
    # overriding __new__ method
    def __new__(cls, clsname, bases, clsdict):
        # if no of base classes is greator than 1
        # raise error
        if len(bases)>1:
            raise TypeError("Inherited multiple base classes!!!")

        # else execute __new__ method of super class, ie.
        # call __init__ of type class
        return super().__new__(cls, clsname, bases, clsdict)

# metaclass can be specified by 'metaclass' keyword argument
# now MultiBase class is used for creating classes
# this will be propagated to all subclasses of Base
class Base(metaclass=MultiBases):
    pass

# no error is raised
class A(Base):
    pass

# no error is raised
class B(Base):
    pass

# This will raise an error!
class C(A, B):
    pass

Note: Decorators can achieve the same code-transformation behavior of metaclasses, but are much simpler.

A quote by Tim Peters

“Metaclasses are deeper magic that 99% of users should never worry about. If you wonder whether you need them, you don’t (the people who actually need them know with certainty that they need them, and don’t need an explanation about why).”

An example

# This metaclass adds a 'hello' method to classes that use the metaclass
# Such classes get a 'hello' method with no extra effort
# The metaclass takes care of that for us

class HelloMeta(type):
    # A hello method
    def hello(cls):
        print("greetings from %s, a HelloMeta type class" % (type(cls())))

    # Call the metaclass
    def __call__(self, *args, **kwargs):
        # create the new class as normal
        cls = type.__call__(self, *args)

        # define a new hello method for each of these classes
        setattr(cls, "hello", self.hello)

        # return the class
        return cls

# Try out the metaclass
class TryHello(object, metaclass=HelloMeta):
    def greet(self):
        self.hello()

# Create an instance of the metaclass. It should automatically have a hello method
# even though one is not defined manually in the class
# in other words, it is added for us by the metaclass
greeter = TryHello()
greeter.greet()

The result of running this code is that the new TryHello class is able to printout a greeting that says:

greetings from <class '__main__.TryHello'>, a HelloMeta type class

The method responsible for this printout is not declared in the declaration of the class. Rather, the metaclass, which is HelloMeta in this case, generates the code at run time that automatically affixes the method to the class.

Rather than get an error for calling a method that does not exist, TryHello gets such a method automatically affixed to it due to using the HelloMeta class as its metaclass.

Metaclasses give us the ability to write code that transforms, not just data, but other code, e.g. transforming a class at the time when it is instantiated. In the example above, our metaclass adds a new method automatically to new classes that we define to use our metaclass as their metaclass.