Python Metaclass

Learn about what is Metaclass in Python, the Type Metaclass, custom Metaclass and the comparison between Metaclass and Inheritance

Image for post
Image for post
Photo by Grace O’Driscoll on Unsplash

Remember in previous blog about ABC (Abstract Base Class) we can pass ? Have you ever wondered what is metaclass and how can we use it?

To understand that, we need to understand the python Class (we are talking about new-style class) first.

The difference between old and new style class is the unity of class and type. Since with x being an instance of a new-style class, , while with old style, :

>>> class Example:
... pass
>>> e = Example()
>>> e.__class__
<class '__main__.Example'>
>>> type(e)
<class '__main__.Example'>
>>> e.__class__ is type(Example)
True

Type Metaclass

In python, is a metaclass, where all classes (new-style) are derived from.

By default, classes are constructed using . The class body is executed in a new namespace and the class name is bound locally to the result of .

class (name, bases, dict)

With one argument, return the type of an object. The return value is a type object and generally the same object as returned by .

With three arguments, return a new type object. This is essentially a dynamic form of the statement.

The name string is the class name and becomes the attribute; the bases tuple itemizes the base classes and becomes the attribute; and the dict dictionary is the namespace containing definitions for class body and is copied to a standard dictionary to become the attribute:



Calling in this manner creates a new instance of the metaclass, aka a new class.

1. with name arg>>> Example = type('Example', (), {})

>>> x = Example()
>>> x
<__main__.Example object at 0x1088c78e0>
2. with name and base (As tuple)and dict>>> AnotherExample = type('AnotherExample', (Example,), dict(attr=1))

>>> x = AnotherExample()
>>> x.attr
1
>>> x.__class__
<class '__main__.AnotherExample'>
>>> x.__class__.__bases__
(<class '__main__.Example'>,)

How does Metaclass work

So you may see Metaclass as an alternative to create class, but you also remember that to create a class we can just use inheritance. So why do we ever need Metaclass?

First, a Metaclass is not really in the Object oriented programming’s sphere. It’s not a part of an object’s class hierarchy whereas base classes are. Metaclass will not appear in the MRO, meaning if an object does it will not search the metaclass for this method.


>>> __metaclass__ = type
>>>
>>> class A:
... pass
>>> a = A()

>>> a
<__main__.A object at 0x4f5cf3>
>>> type(a)
<class '__main__.A'>
>>> A
<class '__main__.A'>
>>> type(A)
<type 'type'>

Thus the Metaclass is used in the construction(__call__) and definition( the other three below) classes.

There are some magic methods that Metaclasses use to manipulate a class:

Create of the class

  • : to instantiate a class;
  • : to initiate values after the instance is created;
  • : to define the class namespace in a mapping that stores the attributes, which means, the attribute that is passed into the metaclass and methods is exactly the same object that is returned by , called before below methods.

Create of a class instance

  • : invoke when create a class instance, invoked after the and methods, which means that unlike these two that get called at class creation time, is called when the already-created class is called to instantiate a new instance of the class.

These methods can be override in custom Metaclass to provide classes with different default behaviour from that of as evidenced below:

class ExampleMeta(type): @classmethod
def __prepare__(metacls, cls, bases):
print(f”Metacls: {metacls} \n cls: {cls} \n bases: {bases}\n”)
print(f”Calling __prepare__ method of {super()}\n”)
return super().__prepare__(cls, bases)
def __new__(metacls, cls, bases, dict):
print(f”Calling __new__ method of {super()}\n”)
return super().__new__(metacls, cls, bases, dict)
def __init__(self, cls, bases, dict):
print(f”Calling __init__ method of {super()}\n”)
super().__init__(cls, bases,dict)

def __call__(self, *args, **kwargs):
print(f”Calling __call__ method of {super()}\n”)
return super().__call__(*args, **kwargs)
class A(metaclass=ExampleMeta):
def __init__(self):
print(“This is when the new class instance gets created\n”)
print(f”Calling __init__ method of {self}”)
===============>AFTER EXECUTION OF ABOVE
Metacls: <class '__main__.ExampleMeta'>
cls: A
bases: ()
Calling __prepare__ method of <super: <class 'ExampleMeta'>, <ExampleMeta object>>Calling __new__ method of <super: <class 'ExampleMeta'>, <ExampleMeta object>>Calling __init__ method of <super: <class 'ExampleMeta'>, <ExampleMeta object>>----------------------------------------------------------a = A()
===============>AFTER EXECUTION OF ABOVE
Calling __call__ method of <super: <class 'ExampleMeta'>, <ExampleMeta object>>This is when the new class instance gets createdCalling __init__ method of <__main__.A object at 0x7f1160225d60>

As you can see above, when reading the class header and body, first it’s the then , .

Only when we create an instance of class , namely, ,will the method gets invoked, and thus invoking and on if defined (in our case only is defined).

Metaclass vs. Inheritance

Now you may wonder, why do we ever need a Metaclass if we have inheritance. To answer that we need to compare the two:

         metaclass                  metaclass
^ ^
| |
instance instance
| |
class B ---inheritance--> class A
^ ^
| |
instance instance
| |
b a

You can see that a metaclass is used for creating class, so it is most commonly used as a class factory. When you create an object by calling the class, Python creates a new class by calling the metaclass through and methods, and you can modify the creation of class instance using . Since you can override these methods in custom Metaclasses and even create your own magic methods (any method with , you are able to do extra things when creating a class.

But be careful with overusing it. And almost always use inheritance when doing OOP as Metaclasses are for working outside those constraints and is almost always not necessary except for some advanced use cases.

For example, if we want to override method then we can’t do it through inheritance, but we can do it through Metaclass. But always use it with care!

In Python, you should make Metaclass your last resort unless you can’t sort it with inheritance or using a decorator pattern.

That’s so much of it today!

Happy Reading!

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store