Metaclass in Python

A Metaclass is the class of a class. Whenever an instance of a class (object) is created, the way that the object behaves is defined by the Class. A Metaclass defines the behavior of the Class itself.


Use of Metaclass in Python

The reason behind the use of Metaclasses is that Python Classes are Objects themselves. Since Classes are Objects, we can do various operations on it, such as assigning it to a variable, copying, etc.

And since they are objects, we can create them dynamically, as we can do for any other object.


To understand the concept of Metaclasses better, we first look at how Python defines Classes. The language defines everything as an object, whether it be an int, a string, or anything else.

To look at the type of any Python object, if you remember, we use the type function.

>>> print(type(123))
<class 'int'>
>>> print(type([1, 2, 3]))
<class 'list'>

>>> class A():
...     def __init__(self):
...             pass
...
>>> a = A()
>>> print(type(a))
<class '__main__.A'>

It returns a class for each case, as we can observe. But to see how Python defines the class itself, we simply look at its type.

>>> print(type(type(123))
<class 'type'>
>>> print(type(A))
<class 'type'>

As you can see, the type(class) is the class type! So it turns out that the class is defined by a class itself? What is this phenomenon?

This is the concept of a metaclass, which serves to define other classes. Basically, it is a class factory, from which other classes such as ints and strs can be defined.

Metaclass
Metaclass hierarchy

type is the metaclass which the language uses to create an object. (which is why every object has a type)

And because type is a metaclass, we can create other classes from it.


Creating Classes Dynamically

We can create classes dynamically by instantiation from the type constructor: type(name, bases, attr)

  • name -> name of the Class
  • bases -> classes from which the new class inherits from
  • attr -> dictionary of attributes + methods contained within the class
>>> Animal = type('Animal', (), dict(__init__ = lambda self: None, worth = lambda self, value: value))

This is the same as:

class Animal():
    def __init__(self):
        pass
    
    def worth(self, value):
        return value

The first bit of code is much easier to write as compared to the second. Writing the class body even during dynamic declaration doesn’t provide much flexibility.

Therefore, Metaclasses provide a powerful and easy way of dynamically creating new classes.


Creating custom Metaclasses

To create our own metaclass, we need to inherit the existing `type` metaclass and override some special methods:

  • __new__() -> This is called before __init__(). It is responsible for creating the object and returns it.
  • __init__() -> This is for initializing the newly created object, which is passed as a parameter (the self parameter)

The following snippet shows how a metaclass can be created:

class MyMetaclass(type):
    def __new__(cls, name, bases, dict):
        print('Creating a new object of', name)
        # Invoke __new__() method of the metaclass type
        return super(MyMetaclass, cls).__new__(cls, name, bases, dict)

    def __init__(cls, name, bases, dict):
        print('Initialising class', name)
        super(MyMetaclass, cls).__init__(name, bases, dict)

Now that we have created our custom Metaclass, we need to make sure we create other classes that use our metaclass.

To do this, we pass the metaclass parameter in the new class definition, which tells the class to use our custom metaclass as it’s own metaclass, instead of type.

class Student(metaclass=MyMetaclass):
    def __init__(self, name):
        self.name = name

    def get_name(self):
        return self.name

Here, Student uses MyMetaclass as its metaclass. Therefore, when creating an instance of Student, our custom metaclass methods will be called, instead of the type metaclass.

stud = Student('Amit')
print(stud.get_name())
print('Type of Student object:', type(stud))
print('Type of Student Class:', type(Student))

Output

Creating a new object of Student
Initialising class Student
Amit
Type of Student object: <class '__main__.Student'>
Type of Student Class: <class '__main__.MyMetaclass'>

NOTE: Old Python versions of 2.7 or below use the __metaclass__ keyword for specifying the metaclass used. Python3 changed this behavior to pass the metaclass as a parameter.


Conclusions

While metaclasses serve as a very powerful way for creating custom APIs and define their behavior during object and Class creation, they are very rarely ever used in practice, as there are other workaround methods for the same.

This article only serves as a starting point for the topic, and about understanding how Python defines everything in terms of the type metaclass.

We looked at how metaclasses can be created, and the methods which are called during the creation of Classes.


References

StackOverflow post on Metaclasses (This provides an in-depth discussion on this topic. Recommended if you want to learn more about metaclasses): https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python