Python is a powerful object oriented programming language used by millions of software developers and organizations. It not only supports classes and objects like other programming languages but also something called as metaclasses. Many programmers often what to know more about metaclasses and get confused about them. In this article, we will learn what are metaclasses in Python, and how to work with them. We will also learn how they are different from traditional classes. They are mainly used in metaprogramming, a practice that allows you to write code to modify the existing code in your application.
Classes as Objects
Before we try to understand metaclasses in python, it is important to learn about regular classes and objects. A class is like a template for an object. It defines the data, attributes and functions that are available to the object. But it has no actual existence. It is only when an object instance is created out of this class, that memory is allocated for the object and not the class. Python provides many pre-built classes as well as allows you to create user-defined classes. Here is an example to create a simple list object from pre-built list class.
>>> sample = list("hello")
You can verify the list using following command
>>> print(sample)
['h', 'e', 'l', 'l', 'o']
You can also check its data type using type() function. In python 3+, you will see the output as class, instead of type.
>>> type(sample)
<class 'list'>
You can also check the attributes & functions available for this class using the dir() function.
>>> print(dir(sample))
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__delslice__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Among them, you can use __class__ attribute to get the information about the object’s class.
>>> print(sample.__class__)
<class 'list'>
This is where it gets interesting. If you call __class__ on the output of above command, you will see that ‘list’ class itself is an instance of ‘type’ class.
>>> print(sample.__class__.__class__)
<class 'type'>
Let us see what happens when we repeat the above steps with a string. We define a string as shown below.
>>> sample = 'hello'
Let us look at its type.
>>> print(type(sample))
<class 'str'>
We see that it is string. Let us check its metaclass.
>>> print(sample.__class__.__class__)
<class 'type'>
You will see that its metaclass is also ‘type’. If you repeat the above steps for other data types, you will get the same result. We see that ‘type’ is the metaclass of all data types in Python.
In other words, everything in Python is an object.
What Are Metaclasses
Metaclass is a class whose instance is a class itself, not an object instance. It allows you to modify the way a class behaves and consequently how its objects also work. Metaclass is like a parent class of a regular class whose attributes and methods are automatically inherited and can be overridden by the metaclass. In simple words, a metaclass is a class that creates other classes. The created classes can create object instances.
Creating Custom Metaclasses
Let us create a very simple metaclass and regular class to understand how to create metaclasses and use them.
class sample_metaclass(type):
pass
class sample_regular_class(metaclass=sample_metaclass):
pass
sample_object=sample_regular_class()
In the above code, we created a simple metaclass of type ‘type’. We have created a regular class sample_regular_class which inherits from this metaclass, via metaclass argument.
Lastly, we create an object instance using this metaclass.
Let us look at the class of object.
>>> print(sample_object.__class__)
<class '__main__.sample_regular_class'>
Here is the class of sample_regular_class.
>>> print(sample_regular_class.__class__)
<class '__main__.sample_metaclass'>
Here is the metaclass of sample_regular_class.
<class 'type'>
It is important to note, that even if you do not specify a metaclass for a class, it is still derived from type metaclass.
There are 2 other methods to create metaclasses in Python – using __new__ and __init__.
Here is how to create metaclasses using __new__
class sample(type):
def __new__(cls, name, bases, dict):
pass
__new__ is used to control metaclass creation by defining dict or bases classes tuples. It returns a class of type cls. It is useful to customize instance creation.
Here is how to create metaclasses in Python using __init__
class sample(type):
def __init__(self, name, bases, dict):
pass
__init__ is usually called after instance creation before initialization.
Benefits of Metaclasses
- Metaclass allows you to reduce code repetition by wrapping common data, attributes and functions for a bunch of classes into a metaclass and making those classes inherit from this metaclass.
- Singleton design – In this case, you can use metaclass to ensure that the same class is not defined more than once. It is used in data connectors to prevent multiple connections to same data source.
- Metaclasses are great for building the basic infrastructure that programmers can leverage to quickly develop applications. Instead of writing everything from scratch, you can write a set of metaclasses with common data, attributes and functions that feed into your classes.
When to Use Metaclasses
Although metaclasses are rarely used, they are required in important situations.
- API Development – Metaclasses are commonly used for API development.
- They are generally used to dynamically create classes, as shown below.
- They are used to modify a hierarchy of classes and subclasses
- They are also used to validate class attributes. For example, if you want to restrict attribute values of data types for a class and its sub classes, you can do it in the metaclass itself.
- If you want to automatically change a class when it is created, you can do so by making it inherit from a metaclass.
Common Examples
Let us look at some common example to use metaclasses.
1. Create Class Dynamically
One of the simplest use cases is to dynamically create Python classes. Generally, type() function is directly called on a data. But you can also pass 2 optional arguments to it. Here is the complete syntax of type().
type(name, bases, dict)
In the above function, name is the name of the class, bases is a tuple of classes from which the class inherits and dict is the namespace directory containing definitions for class.
When we call type() function with all 3 arguments, it will dynamically create a class instance of type metaclass.
Here is how to dynamically create class named sample using metaclasses. Here the bases argument is empty indicating that it does not inherit from any other class. The dict argument is also empty indicating that this class does not have any attributes.
>>> sample = type('sample', (), {})
>>> x = sample()
>>> x
<__main__.sample object at ...>
To make the above code easy to understand, here is how to create the exact same above class the regular way using Class keyword.
>>> class sample:
... pass
...
>>> x = sample()
>>> x
<__main__.sample object at ...>
2. Class Validation
Another simple example is to easily check if a class inherits from multiple classes. For this we first create a sample_meta metaclass. Each metaclass has to generally inherit type metaclass and override __new__() and __init__() functions. __new__() function is called before __init__() and can be used to control how the object is created. We will override this method in the metaclass to check if it inherits from multiple classes.
class sample_meta(type):
# override __new__ method
def __new__(cls, clsname, bases, clsdict):
# if number of base classes > 1 then raise error
if len(bases)>1:
raise TypeError("Inherited multiple base classes")
# else call __init__ of type class
return super().__new__(cls, clsname, bases, clsdict)
Next, we create a regular class using the above metaclass.
class base(metaclass=sample_meta):
pass
Then we create 2 classes that inherit from this base class – One, Two. Both One and Two inherit only one class whereas Three inherits 2 classes – One and Two. Therefore, it will raise an error.
# no error
class One(Base):
pass
# no error
class Two(Base):
pass
# raises error
class Three(One, Two):
pass
You can also do this using decorators but in that case you will need to create separate decorators for each subclass, which is very tedious, especially in big complex applications.
Conclusion
In this article, we have learnt what are metaclasses and how to use them. Metaclasses are classes that allow you to create and modify classes that inherit from it. This is an advanced concept that is not really required for most Python programmers. It is sometimes used to build the basic infrastructure used to speed up software development of complex applications. Nevertheless, if you ever need to work with Metaclasses in python, we hope this article will help you quickly get up and running.
Also read:
What Does Yield Keyword Do In Python
How to Access Correct This Inside Callback
How to Return Response from Asynchronous Call
Sreeram Sreenivasan is the Founder of Ubiq. He has helped many Fortune 500 companies in the areas of BI & software development.