Python Metaclass
Learn about what is Metaclass in Python, the Type Metaclass, custom Metaclass and the comparison between Metaclass and Inheritance
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, theclassdict
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 ABOVECalling __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!