Python Metaclass

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

E.Y.
5 min readJan 15, 2021
Photo by Grace O’Driscoll on Unsplash

Remember in previous blog about ABC (Abstract Base Class) we can pass (metaclass=ABCMeta) ? 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, type(x) is x.__class__ , while with old style, type(x) is type <'instance'> :

>>> 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, type is a metaclass, where all classes (new-style) are derived from.

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

class type(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 object.__class__.

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

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

>>> class X:
... a = 1
...

>>> X = type('X', (object,), dict(a=1))

Calling type() in this manner creates a new instance of the type 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 obj.a_method() 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

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

Create of a class instance

  • __call__: invoke when create a class instance, invoked after the __new__ and __init__ methods, which means that unlike these two that get called at class creation time, __call__ 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 type 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 A header and body, first it’s the __prepare__ then __new__ , __init__ .

Only when we create an instance of class A , namely, a ,will the __call__ method gets invoked, and thus invoking __init__ and __new__ on A if defined (in our case only __init__ 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__init__ and __new__ methods, and you can modify the creation of class instance using __call__ . Since you can override these methods in custom Metaclasses and even create your own magic methods (any method with __method_name__), 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 __init_ 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!

--

--

Responses (1)