Metaclass in Python: a quick introduction

Chuan Zhang
3 min readJan 16, 2024

--

This post quickly reviews how a class is created in Python and explains how metaclass works in an example.

How a Class is Created in Python?

In Python creating a class consists of two major steps:

  • Step 1: prepare the class body and use it to populate namespace of the class
  • Step 2: use the namespace created in step 1 to create the class

The sample code below demonstrates how a class is created under the cover.

>>> # Step 1: populate class namespace
>>>
>>> cls_body = """
... def __init__(self, x, y, z):
... self._x = x
... self._y = y
... self._z = z
... """
>>> cls_namespace = {}
>>> exec(cls_body, globals(), cls_namespace)
>>>
>>>
>>> # Step 2: create new class
>>>
>>> cls_name = 'PointType'
>>> cls_bases = ()
>>> PointType = type(cls_name, cls_bases, cls_namespace)
>>>
>>> PointType
<class '__main__.PointType'>
>>>

Any class is an instance of the type object, and the type class itself inherits from the object class. Just like any class, type can be inherited as well, and it is how metaclass come into being. In next section, we demonstrate how inheriting from type class and overriding the __new__ method can be used to manipulate class creation.

Inheriting type Class

The sample code below demonstrate how class creation can be manipulated by inheriting from the type class and overriding the __new__ method.

>>> class BallType(type):
... def __new__(cls, name, bases, namespace):
... cls_obj = super().__new__(cls, name, bases, namespace)
... cls_obj.volume = lambda self: 4*math.pi*self.radius**3/3.0
... cls_obj.surface_area = lambda self: 4*math.pi*self.radius**2
... return cls_obj
...
>>>
>>> # Step 1: populate class namespace
>>> cls_body = """
... def __init__(self, center: PointType, radius: float):
... self.center = center
... self.radius = radius
... """
>>> cls_namespace = {}
>>> exec(cls_body, globals(), cls_namespace)
>>>
>>> # Step 2: create new class
>>> cls_name = 'Ball'
>>> cls_bases = ()
>>> Ball = BallType(cls_name, cls_bases, cls_namespace)
>>>
>>> b = Ball(PointType(1, 1, 1), 2.0)
>>>
>>> b.volume(), b.surface_area()
(33.510321638291124, 50.26548245743669)
>>>

In the above example, when overriding the __new__ method, we injected two methods, volume and surface_area, into our new BallType. Suppose during the development, we will have multiple different classes, which commonly involve calculation of ball volume and surface area. Then using the above BallType to create these new classes, we don’t have to implement the volume and surface_area methods in any of the new classes.

In actual implementations, however, we should never create new classes as the above example, which is only for demonstrating how it works. In next section, we show in an example what it should look like in actual implementations, using metaclass.

Using Metaclass

>>>
>>> class Planet(metaclass=BallType):
... def __init__(self, name: str, center: PointType, radius: float):
... self.name = name
... self.center = center
... self.radius = radius
...
>>>
>>>
>>> p = Planet('Mars', PointType(1, 2, 1), 3.39)
>>>
>>> p.volume(), p.surface_area()
(163.18780614312308, 144.41398773727704)
>>>
>>>

As demonstrated above, using metaclass when defining a new class, we tell Python to use the specified metaclass, which inherits from the type class and overrides its __new__ method, to create the new class. With metaclass we can customize class creation, and it is usually used in API or library development.

Conclusion

In this post, we have explained with example how a class is created in Python. To recap, it consists of two steps: populating the class namespace using the class body and creating the class as an instance of the type object.

We also demonstrated how class creation can be manipulated by inheriting from the type class and overriding its __new__ method. The we demonstrated how metaclass is used in an example, and hence made how metaclass works clear.

--

--

Chuan Zhang
Chuan Zhang

No responses yet