ℹ️ Skipped - page is already crawled
| Filter | Status | Condition | Details |
|---|---|---|---|
| HTTP status | PASS | download_http_code = 200 | HTTP 200 |
| Age cutoff | PASS | download_stamp > now() - 6 MONTH | 0 months ago |
| History drop | PASS | isNull(history_drop_reason) | No drop reason |
| Spam/ban | PASS | fh_dont_index != 1 AND ml_spam_score = 0 | ml_spam_score=0 |
| Canonical | PASS | meta_canonical IS NULL OR = '' OR = src_unparsed | Not set |
| Property | Value |
|---|---|
| URL | https://towardsdatascience.com/advanced-python-metaclasses-e32d46e0ebe3/ |
| Last Crawled | 2026-04-10 12:48:39 (1 day ago) |
| First Indexed | 2025-03-13 04:52:48 (1 year ago) |
| HTTP Status Code | 200 |
| Meta Title | Advanced Python: Metaclasses | Towards Data Science |
| Meta Description | null |
| Meta Canonical | null |
| Boilerpipe Text | As Atlas is to the heavens, metaclasses are to classes. Photo by
Alexander Nikitenko
on
Unsplash
This
article
continues the Advanced Python series (previous article on functions in Python). This time, I cover an introduction to metaclasses. The subject is rather advanced because there is rarely a need for the engineer to implement custom metaclasses. However, it is one of the most important constructs and mechanisms that every knowledgeable Python developer should know about, mainly because it enables the OOP paradigm.
After getting the idea behind metaclasses and how class objects are created, you will be able to continue learning the OOP principles of encapsulation, abstraction, inheritance, and polymorphism. Then you will be able to understand how to apply all of it through numerous design patterns guided by some of the principles in software engineering (for example,
SOLID
).
Now, let’s start with this seemingly trivial example:
class Person:
pass
class Child(Person):
pass
child = Child()
Back in the days when you learned about object-oriented programming, you most probably came across a general idea that describes what classes and objects are, and it goes like this:
"Class is like a cookie mold. And objects are cookies molded by it".
This is a very intuitive explanation and conveys the idea rather clearly. Having said that, our example defines two templates with little or no functionalities, but they work. You can play with defining the
__init__
method, set some object attributes, and make it more usable.
However, what is interesting
in Python
is that
even though a class is a "template" that is used to create objects from it, it is also an object itself.
Everyone learning OOP in Python would quickly go over this statement, not really thinking in depth. Everything in Python is an object, so what? But once you start thinking about this, a lot of questions pop up, and interesting Python intricacies unravel.
Before I start asking these questions for you, let’s remember that
in Python, everything is an object
. And I mean everything. This is probably something you already picked up, even if you are a newbie. The next example shows this:
class Person:
pass
id(Person)
# some memory location
class Child(Person):
pass
id(Child)
# some memory location
# Class objects are created, you can instantiate objects
child = Child()
id(child)
# some memory location
Based on these examples, here are some questions that you should ask yourself:
If a class is an object, when is it created?
Who creates class objects?
If class is an object, how come I’m able to call it when instantiating an object?
Class object creation
Python is widely known as an interpreted language. This means there is an interpreter (program or process) that goes line by line and tries to translate it to machine code. This is opposed to compiled programming languages like C, where programming code is translated into machine code before you run it. This is a very simplified view. To be more precise, Python is both compiled and interpreted, but this is a subject for another time. What is important for our example is that the interpreter goes through the class definition, and once the class code block is finished, the class object is created. From then on, you are able to instantiate objects from it. You have to do this explicitly, of course, even though class objects are instantiated implicitly.
But what "process" is triggered when the interpreter finishes reading the class code block? We could go directly to details, but one chart speaks a thousand words:
How objects, classes and metaclasses are related to each other. Image by Ilija Lazarevic.
If you are not aware, Python has
type
functions that can be used for our purpose(s) now. By calling
type
with object as an argument, you will get the object’s type. How ingenious! Take a look:
class Person:
pass
class Child(Person):
pass
child = Child()
type(child)
# Child
type(Child)
# type
The
type
call in the example makes sense.
child
is of type
Child
. We used a class object to create it. So in some way, you can think of
type(child)
giving you the name of its "creator". And in a way, the
Child
class is its creator because you called it to create a new instance. But what happens when you try to get the "creator" of the class object,
type(Child)
? You get the
type
. To sum it up,
an object is an instance of a class, and a class is an instance of a type
. By now, you may be wondering how a class is an instance of a function, and the answer is that
type
is both a function and a class. This is intentionally left as it is because of backward compatibility back in the old days.
What will make your head spin is the name we have for the class that is used to create a class object. It is called a
metaclass.
And here it is important to make a distinction between inheritance from the perspective of the object-oriented paradigm and the mechanisms of a language that enable you to practice this paradigm. Metaclasses provide this mechanism. What can be even more confusing is that metaclasses are able to inherit parent classes just like regular ones can. But this can quickly become "inceptional" programming, so let’s not go that deep.
Do we have to deal with these metaclasses on a daily basis? Well, no. In rare cases, you will probably need to define and use them, but most of the time, default behavior is just fine.
Let’s continue with our journey, this time with a new example:
class Parent:
def __init__(self, name, age):
self.name = name
self.age = age
parent = Parent('John', 35)
Something like this should be your very first OOP step in Python. You are taught that
__init__
is a constructor where you set the values of your object attributes, and you are good to go. However, this
__init__
dunder method is exactly what it says: the initialization step. Isn’t it strange that you call it to initialize an object, and yet you get an object instance in return? There is no
return
there, right? So, how is this possible? Who returns the instance of a class?
Very few learn at the start of their Python journey that there is another method that is called implicitly and is named
__new__
. This method actually creates an instance before
__init__
is called to initialize it. Here is an example:
class Parent:
def __new__(cls, name, age):
print('new is called')
return super().__new__(cls)
def __init__(self, name, age):
print('init is called')
self.name = name
self.age = age
parent = Parent('John', 35)
# new is called
# init is called
What you’ll immediately see is that
__new__
returns
super().__new__(cls)
. This is a new instance.
super()
fetches the parent class of
Parent,
which is implicitly an
object
class. This class is inherited by all classes in Python. And it is an object in itself too. Another innovative move by the Python creators!
isinstance(object, object)
# True
But what binds
__new__
and
__init__
? There has to be something more to how object instantiation is performed when we call
Parent('John' ,35)
. Take a look at it once again. You are invoking (calling) a class object, like a function.
Python callable
Python, being a structurally typed language, enables you to define specific methods in your class that describe a
Protocol
(a way of using its object), and based on this, all instances of a class will behave in the expected way. Do not get intimidated if you are coming from other programming languages.
Protocols
are something like
Interfaces
in other languages. However, here we do not explicitly state that we are implementing a specific interface and, therefore, specific behavior. We just implement methods that are described by
Protocol
, and all objects are going to have protocol’s behavior. One of these
Protocols
is
Callable
. By implementing the dunder method
__call__,
you enable your object to be called like a function. See the example:
class Parent:
def __new__(cls, name, age):
print('new is called')
return super().__new__(cls)
def __init__(self, name, age):
print('init is called')
self.name = name
self.age = age
def __call__(self):
print('Parent here!')
parent = Parent('John', 35)
parent()
# Parent here!
By implementing
__call__
in the class definition, your class instances become C
allable
. But what about
Parent('John', 35)
? How do you achieve the same with your class object? If an object’s type definition (class) specifies that the object is
Callable
, then the class object type (type i.e. metaclass) should specify that the class object is callable too, right? Invocation of the dunder methods
__new__
and
__init__
happens there.
At this point, it is time to start playing with metaclasses.
Python metaclasses
There are at least two ways you can change the process of class object creation. One is by using class decorators; the other is by explicitly specifying a metalcass. I will describe the metaclass approach. Keep in mind that a metaclass looks like a regular class, and the only exception is that it has to inherit a
type
class. Why? Because
type
classes have all the implementation that is required for our code to still work as expected. For example:
class MyMeta(type):
def __call__(self, *args, **kwargs):
print(f'{self.__name__} is called'
f' with args={args}, kwargs={kwarg}')
class Parent(metaclass=MyMeta):
def __new__(cls, name, age):
print('new is called')
return super().__new__(cls)
def __init__(self, name, age):
print('init is called')
self.name = name
self.age = age
parent = Parent('John', 35)
# Parent is called with args=('John', 35), kwargs={}
type(parent)
# NoneType
Here,
MyMeta
is the driving force behind new class object instantiation and also specifies how new class instances are created. Take a closer look at the last two lines of the example.
parent
holds nothing! But why? Because, as you can see,
MyMeta.__call__
just prints information and returns nothing. Explicitly, that is. Implicitly, that means that it returns
None
, which is of
NoneType
.
How should we fix this?
class MyMeta(type):
def __call__(cls, *args, **kwargs):
print(f'{cls.__name__} is called'
f'with args={args}, kwargs={kwargs}')
print('metaclass calls __new__')
obj = cls.__new__(cls, *args, **kwargs)
if isinstance(obj, cls):
print('metaclass calls __init__')
cls.__init__(obj, *args, **kwargs)
return obj
class Parent(metaclass=MyMeta):
def __new__(cls, name, age):
print('new is called')
return super().__new__(cls)
def __init__(self, name, age):
print('init is called')
self.name = name
self.age = age
parent = Parent('John', 35)
# Parent is called with args=('John', 35), kwargs={}
# metaclass calls __new__
# new is called
# metaclass calls __init__
# init is called
type(parent)
# Parent
str(parent)
# '<__main__.Parent object at 0x103d540a0>'
From the output, you can see what happens on
MyMeta.__call__
invocation. The provided implementation is just an example of how the whole thing works. You should be more careful if you plan to override parts of metaclasses yourself. There are some edge cases that you have to cover up. For example, one of the edge cases is that
Parent.__new__
can return an object that is not an instance of the
Parent
class. In that case, it is not going to be initialized by the
Parent.__init__
method. That is the expected behavior you have to be aware of, and it really doesn’t make sense to initialize an object that is not an instance of the same class.
Conclusion
This would conclude the brief overview of what happens when you define a class and make an instance of it. Of course, you could go even further and see what happens during the class block interpretation. All of this happens in the metaclass too. It’s fortunate for most of us that we probably won’t need to create and use specific metaclasses. Yet it is useful to understand how everything functions. I’d like to use a similar saying that applies to using NoSQL databases, which goes something like this: if you’re not sure whether you need to use Python metaclasses, you probably don’t.
References
Python Metaclasses
Understanding Object Instantiation and Metaclasses in Python |
| Markdown | [](https://towardsdatascience.com/)
Publish AI, ML & data-science insights to a global community of data professionals.
[Sign in]()
[Submit an Article](https://contributor.insightmediagroup.io/)
- [Latest](https://towardsdatascience.com/latest/)
- [Editor’s Picks](https://towardsdatascience.com/tag/editors-pick/)
- [Deep Dives](https://towardsdatascience.com/tag/deep-dives/)
- [Newsletter](https://towardsdatascience.com/tag/the-variable/)
- [Write For TDS](https://towardsdatascience.com/submissions/)
Toggle Mobile Navigation
- [LinkedIn](https://www.linkedin.com/company/towards-data-science/?originalSubdomain=ca)
- [X](https://x.com/TDataScience)
Toggle Search
[Data Science](https://towardsdatascience.com/category/data-science/)
# Advanced Python: Metaclasses
A brief introduction to Python class object and how it is created
[Ilija Lazarevic](https://towardsdatascience.com/author/ilija-lazarevic/)
Oct 6, 2023
10 min read
Share

As Atlas is to the heavens, metaclasses are to classes. Photo by [Alexander Nikitenko](https://unsplash.com/@quintonik?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/photos/H6obC_biCSk?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)
This [article](https://medium.com/towards-data-science/advanced-python-functions-3be6810f92d1) continues the Advanced Python series (previous article on functions in Python). This time, I cover an introduction to metaclasses. The subject is rather advanced because there is rarely a need for the engineer to implement custom metaclasses. However, it is one of the most important constructs and mechanisms that every knowledgeable Python developer should know about, mainly because it enables the OOP paradigm.
After getting the idea behind metaclasses and how class objects are created, you will be able to continue learning the OOP principles of encapsulation, abstraction, inheritance, and polymorphism. Then you will be able to understand how to apply all of it through numerous design patterns guided by some of the principles in software engineering (for example, *SOLID*).
***
Now, let’s start with this seemingly trivial example:
```
class Person:
pass
class Child(Person):
pass
child = Child()
```
Back in the days when you learned about object-oriented programming, you most probably came across a general idea that describes what classes and objects are, and it goes like this:
> "Class is like a cookie mold. And objects are cookies molded by it".
This is a very intuitive explanation and conveys the idea rather clearly. Having said that, our example defines two templates with little or no functionalities, but they work. You can play with defining the `__init__` method, set some object attributes, and make it more usable.
However, what is interesting **in Python** is that **even though a class is a "template" that is used to create objects from it, it is also an object itself.** Everyone learning OOP in Python would quickly go over this statement, not really thinking in depth. Everything in Python is an object, so what? But once you start thinking about this, a lot of questions pop up, and interesting Python intricacies unravel.
Before I start asking these questions for you, let’s remember that **in Python, everything is an object**. And I mean everything. This is probably something you already picked up, even if you are a newbie. The next example shows this:
```
class Person:
pass
id(Person)
# some memory location
class Child(Person):
pass
id(Child)
# some memory location
# Class objects are created, you can instantiate objects
child = Child()
id(child)
# some memory location
```
Based on these examples, here are some questions that you should ask yourself:
- If a class is an object, when is it created?
- Who creates class objects?
- If class is an object, how come I’m able to call it when instantiating an object?
## Class object creation
Python is widely known as an interpreted language. This means there is an interpreter (program or process) that goes line by line and tries to translate it to machine code. This is opposed to compiled programming languages like C, where programming code is translated into machine code before you run it. This is a very simplified view. To be more precise, Python is both compiled and interpreted, but this is a subject for another time. What is important for our example is that the interpreter goes through the class definition, and once the class code block is finished, the class object is created. From then on, you are able to instantiate objects from it. You have to do this explicitly, of course, even though class objects are instantiated implicitly.
But what "process" is triggered when the interpreter finishes reading the class code block? We could go directly to details, but one chart speaks a thousand words:

How objects, classes and metaclasses are related to each other. Image by Ilija Lazarevic.
If you are not aware, Python has `type` functions that can be used for our purpose(s) now. By calling `type` with object as an argument, you will get the object’s type. How ingenious! Take a look:
```
class Person:
pass
class Child(Person):
pass
child = Child()
type(child)
# Child
type(Child)
# type
```
The `type` call in the example makes sense. `child` is of type `Child`. We used a class object to create it. So in some way, you can think of `type(child)` giving you the name of its "creator". And in a way, the `Child` class is its creator because you called it to create a new instance. But what happens when you try to get the "creator" of the class object, `type(Child)`? You get the `type`. To sum it up, **an object is an instance of a class, and a class is an instance of a type**. By now, you may be wondering how a class is an instance of a function, and the answer is that`type` is both a function and a class. This is intentionally left as it is because of backward compatibility back in the old days.
What will make your head spin is the name we have for the class that is used to create a class object. It is called a **metaclass.** And here it is important to make a distinction between inheritance from the perspective of the object-oriented paradigm and the mechanisms of a language that enable you to practice this paradigm. Metaclasses provide this mechanism. What can be even more confusing is that metaclasses are able to inherit parent classes just like regular ones can. But this can quickly become "inceptional" programming, so let’s not go that deep.
Do we have to deal with these metaclasses on a daily basis? Well, no. In rare cases, you will probably need to define and use them, but most of the time, default behavior is just fine.
Let’s continue with our journey, this time with a new example:
```
class Parent:
def __init__(self, name, age):
self.name = name
self.age = age
parent = Parent('John', 35)
```
Something like this should be your very first OOP step in Python. You are taught that `__init__` is a constructor where you set the values of your object attributes, and you are good to go. However, this `__init__` dunder method is exactly what it says: the initialization step. Isn’t it strange that you call it to initialize an object, and yet you get an object instance in return? There is no `return` there, right? So, how is this possible? Who returns the instance of a class?
Very few learn at the start of their Python journey that there is another method that is called implicitly and is named `__new__`. This method actually creates an instance before `__init__` is called to initialize it. Here is an example:
```
class Parent:
def __new__(cls, name, age):
print('new is called')
return super().__new__(cls)
def __init__(self, name, age):
print('init is called')
self.name = name
self.age = age
parent = Parent('John', 35)
# new is called
# init is called
```
What you’ll immediately see is that `__new__` returns `super().__new__(cls)`. This is a new instance. `super()` fetches the parent class of `Parent,` which is implicitly an `object` class. This class is inherited by all classes in Python. And it is an object in itself too. Another innovative move by the Python creators\!
```
isinstance(object, object)
# True
```
But what binds `__new__` and `__init__`? There has to be something more to how object instantiation is performed when we call `Parent('John' ,35)`. Take a look at it once again. You are invoking (calling) a class object, like a function.
## Python callable
Python, being a structurally typed language, enables you to define specific methods in your class that describe a *Protocol* (a way of using its object), and based on this, all instances of a class will behave in the expected way. Do not get intimidated if you are coming from other programming languages. *Protocols* are something like *Interfaces* in other languages. However, here we do not explicitly state that we are implementing a specific interface and, therefore, specific behavior. We just implement methods that are described by *Protocol*, and all objects are going to have protocol’s behavior. One of these *Protocols* is *Callable*. By implementing the dunder method `__call__,` you enable your object to be called like a function. See the example:
```
class Parent:
def __new__(cls, name, age):
print('new is called')
return super().__new__(cls)
def __init__(self, name, age):
print('init is called')
self.name = name
self.age = age
def __call__(self):
print('Parent here!')
parent = Parent('John', 35)
parent()
# Parent here!
```
By implementing `__call__` in the class definition, your class instances become C*allable*. But what about `Parent('John', 35)`? How do you achieve the same with your class object? If an object’s type definition (class) specifies that the object is *Callable*, then the class object type (type i.e. metaclass) should specify that the class object is callable too, right? Invocation of the dunder methods `__new__` and `__init__` happens there.
At this point, it is time to start playing with metaclasses.
## Python metaclasses
There are at least two ways you can change the process of class object creation. One is by using class decorators; the other is by explicitly specifying a metalcass. I will describe the metaclass approach. Keep in mind that a metaclass looks like a regular class, and the only exception is that it has to inherit a `type` class. Why? Because `type` classes have all the implementation that is required for our code to still work as expected. For example:
```
class MyMeta(type):
def __call__(self, *args, **kwargs):
print(f'{self.__name__} is called'
f' with args={args}, kwargs={kwarg}')
class Parent(metaclass=MyMeta):
def __new__(cls, name, age):
print('new is called')
return super().__new__(cls)
def __init__(self, name, age):
print('init is called')
self.name = name
self.age = age
parent = Parent('John', 35)
# Parent is called with args=('John', 35), kwargs={}
type(parent)
# NoneType
```
Here, `MyMeta` is the driving force behind new class object instantiation and also specifies how new class instances are created. Take a closer look at the last two lines of the example. `parent` holds nothing! But why? Because, as you can see, `MyMeta.__call__` just prints information and returns nothing. Explicitly, that is. Implicitly, that means that it returns `None`, which is of `NoneType`.
How should we fix this?
```
class MyMeta(type):
def __call__(cls, *args, **kwargs):
print(f'{cls.__name__} is called'
f'with args={args}, kwargs={kwargs}')
print('metaclass calls __new__')
obj = cls.__new__(cls, *args, **kwargs)
if isinstance(obj, cls):
print('metaclass calls __init__')
cls.__init__(obj, *args, **kwargs)
return obj
class Parent(metaclass=MyMeta):
def __new__(cls, name, age):
print('new is called')
return super().__new__(cls)
def __init__(self, name, age):
print('init is called')
self.name = name
self.age = age
parent = Parent('John', 35)
# Parent is called with args=('John', 35), kwargs={}
# metaclass calls __new__
# new is called
# metaclass calls __init__
# init is called
type(parent)
# Parent
str(parent)
# '<__main__.Parent object at 0x103d540a0>'
```
From the output, you can see what happens on `MyMeta.__call__`invocation. The provided implementation is just an example of how the whole thing works. You should be more careful if you plan to override parts of metaclasses yourself. There are some edge cases that you have to cover up. For example, one of the edge cases is that `Parent.__new__` can return an object that is not an instance of the `Parent` class. In that case, it is not going to be initialized by the `Parent.__init__` method. That is the expected behavior you have to be aware of, and it really doesn’t make sense to initialize an object that is not an instance of the same class.
## Conclusion
This would conclude the brief overview of what happens when you define a class and make an instance of it. Of course, you could go even further and see what happens during the class block interpretation. All of this happens in the metaclass too. It’s fortunate for most of us that we probably won’t need to create and use specific metaclasses. Yet it is useful to understand how everything functions. I’d like to use a similar saying that applies to using NoSQL databases, which goes something like this: if you’re not sure whether you need to use Python metaclasses, you probably don’t.
## References
- [Python Metaclasses](https://jfreeman.dev/blog/2020/12/07/python-metaclasses/)
- [Understanding Object Instantiation and Metaclasses in Python](https://www.honeybadger.io/blog/python-instantiation-metaclass/)
***
Written By
Ilija Lazarevic
[See all from Ilija Lazarevic](https://towardsdatascience.com/author/ilija-lazarevic/)
[Data Science](https://towardsdatascience.com/tag/data-science/), [Machine Learning](https://towardsdatascience.com/tag/machine-learning/), [Oop](https://towardsdatascience.com/tag/oop/), [Programming](https://towardsdatascience.com/tag/programming/), [Python](https://towardsdatascience.com/tag/python/)
Share This Article
- [Share on Facebook](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Ftowardsdatascience.com%2Fadvanced-python-metaclasses-e32d46e0ebe3%2F&title=Advanced%20Python%3A%20Metaclasses)
- [Share on LinkedIn](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Ftowardsdatascience.com%2Fadvanced-python-metaclasses-e32d46e0ebe3%2F&title=Advanced%20Python%3A%20Metaclasses)
- [Share on X](https://x.com/share?url=https%3A%2F%2Ftowardsdatascience.com%2Fadvanced-python-metaclasses-e32d46e0ebe3%2F&text=Advanced%20Python%3A%20Metaclasses)
Towards Data Science is a community publication. Submit your insights to reach our global audience and earn through the TDS Author Payment Program.
[Write for TDS](https://towardsdatascience.com/questions-96667b06af5/)
## Related Articles
- 
## [Implementing Convolutional Neural Networks in TensorFlow](https://towardsdatascience.com/implementing-convolutional-neural-networks-in-tensorflow-bc1c4f00bd34/)
[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)
Step-by-step code guide to building a Convolutional Neural Network
[Shreya Rao](https://towardsdatascience.com/author/shreya-rao/)
August 20, 2024
6 min read
- 
## [How to Forecast Hierarchical Time Series](https://towardsdatascience.com/how-to-forecast-hierarchical-time-series-75f223f79793/)
[Artificial Intelligence](https://towardsdatascience.com/category/artificial-intelligence/)
A beginner’s guide to forecast reconciliation
[Dr. Robert KĂĽbler](https://towardsdatascience.com/author/dr-robert-kuebler/)
August 20, 2024
13 min read
- 
## [Hands-on Time Series Anomaly Detection using Autoencoders, with Python](https://towardsdatascience.com/hands-on-time-series-anomaly-detection-using-autoencoders-with-python-7cd893bbc122/)
[Data Science](https://towardsdatascience.com/category/data-science/)
Here’s how to use Autoencoders to detect signals with anomalies in a few lines of…
[Piero Paialunga](https://towardsdatascience.com/author/piero-paialunga/)
August 21, 2024
12 min read
- 
## [3 AI Use Cases (That Are Not a Chatbot)](https://towardsdatascience.com/3-ai-use-cases-that-are-not-a-chatbot-f4f328a2707a/)
[Machine Learning](https://towardsdatascience.com/category/artificial-intelligence/machine-learning/)
Feature engineering, structuring unstructured data, and lead scoring
[Shaw Talebi](https://towardsdatascience.com/author/shawhin/)
August 21, 2024
7 min read
- ## [Solving a Constrained Project Scheduling Problem with Quantum Annealing](https://towardsdatascience.com/solving-a-constrained-project-scheduling-problem-with-quantum-annealing-d0640e657a3b/)
[Data Science](https://towardsdatascience.com/category/data-science/)
Solving the resource constrained project scheduling problem (RCPSP) with D-Wave’s hybrid constrained quadratic model (CQM)
[Luis Fernando PÉREZ ARMAS, Ph.D.](https://towardsdatascience.com/author/luisfernandopa1212/)
August 20, 2024
29 min read
- 
## [Back To Basics, Part Uno: Linear Regression and Cost Function](https://towardsdatascience.com/back-to-basics-part-uno-linear-regression-cost-function-and-gradient-descent-590dcb3eee46/)
[Data Science](https://towardsdatascience.com/category/data-science/)
An illustrated guide on essential machine learning concepts
[Shreya Rao](https://towardsdatascience.com/author/shreya-rao/)
February 3, 2023
6 min read
- 
## [Must-Know in Statistics: The Bivariate Normal Projection Explained](https://towardsdatascience.com/must-know-in-statistics-the-bivariate-normal-projection-explained-ace7b2f70b5b/)
[Data Science](https://towardsdatascience.com/category/data-science/)
Derivation and practical examples of this powerful concept
[Luigi Battistoni](https://towardsdatascience.com/author/lu-battistoni/)
August 14, 2024
7 min read
- [YouTube](https://www.youtube.com/c/TowardsDataScience)
- [X](https://x.com/TDataScience)
- [LinkedIn](https://www.linkedin.com/company/towards-data-science/?originalSubdomain=ca)
- [Threads](https://www.threads.net/@towardsdatascience)
- [Bluesky](https://bsky.app/profile/towardsdatascience.com)
[](https://towardsdatascience.com/)
Your home for data science and Al. The world’s leading publication for data science, data analytics, data engineering, machine learning, and artificial intelligence professionals.
© Insight Media Group, LLC 2026
Subscribe to Our Newsletter
- [Write For TDS](https://towardsdatascience.com/questions-96667b06af5/)
- [About](https://towardsdatascience.com/about-towards-data-science-d691af11cc2f/)
- [Advertise](https://contact.towardsdatascience.com/advertise-with-towards-data-science)
- [Privacy Policy](https://towardsdatascience.com/privacy-policy/)
- [Terms of Use](https://towardsdatascience.com/website-terms-of-use/)
 |
| Readable Markdown | 
As Atlas is to the heavens, metaclasses are to classes. Photo by [Alexander Nikitenko](https://unsplash.com/@quintonik?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/photos/H6obC_biCSk?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)
This [article](https://medium.com/towards-data-science/advanced-python-functions-3be6810f92d1) continues the Advanced Python series (previous article on functions in Python). This time, I cover an introduction to metaclasses. The subject is rather advanced because there is rarely a need for the engineer to implement custom metaclasses. However, it is one of the most important constructs and mechanisms that every knowledgeable Python developer should know about, mainly because it enables the OOP paradigm.
After getting the idea behind metaclasses and how class objects are created, you will be able to continue learning the OOP principles of encapsulation, abstraction, inheritance, and polymorphism. Then you will be able to understand how to apply all of it through numerous design patterns guided by some of the principles in software engineering (for example, *SOLID*).
***
Now, let’s start with this seemingly trivial example:
```
class Person:
pass
class Child(Person):
pass
child = Child()
```
Back in the days when you learned about object-oriented programming, you most probably came across a general idea that describes what classes and objects are, and it goes like this:
> "Class is like a cookie mold. And objects are cookies molded by it".
This is a very intuitive explanation and conveys the idea rather clearly. Having said that, our example defines two templates with little or no functionalities, but they work. You can play with defining the `__init__` method, set some object attributes, and make it more usable.
However, what is interesting **in Python** is that **even though a class is a "template" that is used to create objects from it, it is also an object itself.** Everyone learning OOP in Python would quickly go over this statement, not really thinking in depth. Everything in Python is an object, so what? But once you start thinking about this, a lot of questions pop up, and interesting Python intricacies unravel.
Before I start asking these questions for you, let’s remember that **in Python, everything is an object**. And I mean everything. This is probably something you already picked up, even if you are a newbie. The next example shows this:
```
class Person:
pass
id(Person)
# some memory location
class Child(Person):
pass
id(Child)
# some memory location
# Class objects are created, you can instantiate objects
child = Child()
id(child)
# some memory location
```
Based on these examples, here are some questions that you should ask yourself:
- If a class is an object, when is it created?
- Who creates class objects?
- If class is an object, how come I’m able to call it when instantiating an object?
## Class object creation
Python is widely known as an interpreted language. This means there is an interpreter (program or process) that goes line by line and tries to translate it to machine code. This is opposed to compiled programming languages like C, where programming code is translated into machine code before you run it. This is a very simplified view. To be more precise, Python is both compiled and interpreted, but this is a subject for another time. What is important for our example is that the interpreter goes through the class definition, and once the class code block is finished, the class object is created. From then on, you are able to instantiate objects from it. You have to do this explicitly, of course, even though class objects are instantiated implicitly.
But what "process" is triggered when the interpreter finishes reading the class code block? We could go directly to details, but one chart speaks a thousand words:

How objects, classes and metaclasses are related to each other. Image by Ilija Lazarevic.
If you are not aware, Python has `type` functions that can be used for our purpose(s) now. By calling `type` with object as an argument, you will get the object’s type. How ingenious! Take a look:
```
class Person:
pass
class Child(Person):
pass
child = Child()
type(child)
# Child
type(Child)
# type
```
The `type` call in the example makes sense. `child` is of type `Child`. We used a class object to create it. So in some way, you can think of `type(child)` giving you the name of its "creator". And in a way, the `Child` class is its creator because you called it to create a new instance. But what happens when you try to get the "creator" of the class object, `type(Child)`? You get the `type`. To sum it up, **an object is an instance of a class, and a class is an instance of a type**. By now, you may be wondering how a class is an instance of a function, and the answer is that`type` is both a function and a class. This is intentionally left as it is because of backward compatibility back in the old days.
What will make your head spin is the name we have for the class that is used to create a class object. It is called a **metaclass.** And here it is important to make a distinction between inheritance from the perspective of the object-oriented paradigm and the mechanisms of a language that enable you to practice this paradigm. Metaclasses provide this mechanism. What can be even more confusing is that metaclasses are able to inherit parent classes just like regular ones can. But this can quickly become "inceptional" programming, so let’s not go that deep.
Do we have to deal with these metaclasses on a daily basis? Well, no. In rare cases, you will probably need to define and use them, but most of the time, default behavior is just fine.
Let’s continue with our journey, this time with a new example:
```
class Parent:
def __init__(self, name, age):
self.name = name
self.age = age
parent = Parent('John', 35)
```
Something like this should be your very first OOP step in Python. You are taught that `__init__` is a constructor where you set the values of your object attributes, and you are good to go. However, this `__init__` dunder method is exactly what it says: the initialization step. Isn’t it strange that you call it to initialize an object, and yet you get an object instance in return? There is no `return` there, right? So, how is this possible? Who returns the instance of a class?
Very few learn at the start of their Python journey that there is another method that is called implicitly and is named `__new__`. This method actually creates an instance before `__init__` is called to initialize it. Here is an example:
```
class Parent:
def __new__(cls, name, age):
print('new is called')
return super().__new__(cls)
def __init__(self, name, age):
print('init is called')
self.name = name
self.age = age
parent = Parent('John', 35)
# new is called
# init is called
```
What you’ll immediately see is that `__new__` returns `super().__new__(cls)`. This is a new instance. `super()` fetches the parent class of `Parent,` which is implicitly an `object` class. This class is inherited by all classes in Python. And it is an object in itself too. Another innovative move by the Python creators\!
```
isinstance(object, object)
# True
```
But what binds `__new__` and `__init__`? There has to be something more to how object instantiation is performed when we call `Parent('John' ,35)`. Take a look at it once again. You are invoking (calling) a class object, like a function.
## Python callable
Python, being a structurally typed language, enables you to define specific methods in your class that describe a *Protocol* (a way of using its object), and based on this, all instances of a class will behave in the expected way. Do not get intimidated if you are coming from other programming languages. *Protocols* are something like *Interfaces* in other languages. However, here we do not explicitly state that we are implementing a specific interface and, therefore, specific behavior. We just implement methods that are described by *Protocol*, and all objects are going to have protocol’s behavior. One of these *Protocols* is *Callable*. By implementing the dunder method `__call__,` you enable your object to be called like a function. See the example:
```
class Parent:
def __new__(cls, name, age):
print('new is called')
return super().__new__(cls)
def __init__(self, name, age):
print('init is called')
self.name = name
self.age = age
def __call__(self):
print('Parent here!')
parent = Parent('John', 35)
parent()
# Parent here!
```
By implementing `__call__` in the class definition, your class instances become C*allable*. But what about `Parent('John', 35)`? How do you achieve the same with your class object? If an object’s type definition (class) specifies that the object is *Callable*, then the class object type (type i.e. metaclass) should specify that the class object is callable too, right? Invocation of the dunder methods `__new__` and `__init__` happens there.
At this point, it is time to start playing with metaclasses.
## Python metaclasses
There are at least two ways you can change the process of class object creation. One is by using class decorators; the other is by explicitly specifying a metalcass. I will describe the metaclass approach. Keep in mind that a metaclass looks like a regular class, and the only exception is that it has to inherit a `type` class. Why? Because `type` classes have all the implementation that is required for our code to still work as expected. For example:
```
class MyMeta(type):
def __call__(self, *args, **kwargs):
print(f'{self.__name__} is called'
f' with args={args}, kwargs={kwarg}')
class Parent(metaclass=MyMeta):
def __new__(cls, name, age):
print('new is called')
return super().__new__(cls)
def __init__(self, name, age):
print('init is called')
self.name = name
self.age = age
parent = Parent('John', 35)
# Parent is called with args=('John', 35), kwargs={}
type(parent)
# NoneType
```
Here, `MyMeta` is the driving force behind new class object instantiation and also specifies how new class instances are created. Take a closer look at the last two lines of the example. `parent` holds nothing! But why? Because, as you can see, `MyMeta.__call__` just prints information and returns nothing. Explicitly, that is. Implicitly, that means that it returns `None`, which is of `NoneType`.
How should we fix this?
```
class MyMeta(type):
def __call__(cls, *args, **kwargs):
print(f'{cls.__name__} is called'
f'with args={args}, kwargs={kwargs}')
print('metaclass calls __new__')
obj = cls.__new__(cls, *args, **kwargs)
if isinstance(obj, cls):
print('metaclass calls __init__')
cls.__init__(obj, *args, **kwargs)
return obj
class Parent(metaclass=MyMeta):
def __new__(cls, name, age):
print('new is called')
return super().__new__(cls)
def __init__(self, name, age):
print('init is called')
self.name = name
self.age = age
parent = Parent('John', 35)
# Parent is called with args=('John', 35), kwargs={}
# metaclass calls __new__
# new is called
# metaclass calls __init__
# init is called
type(parent)
# Parent
str(parent)
# '<__main__.Parent object at 0x103d540a0>'
```
From the output, you can see what happens on `MyMeta.__call__`invocation. The provided implementation is just an example of how the whole thing works. You should be more careful if you plan to override parts of metaclasses yourself. There are some edge cases that you have to cover up. For example, one of the edge cases is that `Parent.__new__` can return an object that is not an instance of the `Parent` class. In that case, it is not going to be initialized by the `Parent.__init__` method. That is the expected behavior you have to be aware of, and it really doesn’t make sense to initialize an object that is not an instance of the same class.
## Conclusion
This would conclude the brief overview of what happens when you define a class and make an instance of it. Of course, you could go even further and see what happens during the class block interpretation. All of this happens in the metaclass too. It’s fortunate for most of us that we probably won’t need to create and use specific metaclasses. Yet it is useful to understand how everything functions. I’d like to use a similar saying that applies to using NoSQL databases, which goes something like this: if you’re not sure whether you need to use Python metaclasses, you probably don’t.
## References
- [Python Metaclasses](https://jfreeman.dev/blog/2020/12/07/python-metaclasses/)
- [Understanding Object Instantiation and Metaclasses in Python](https://www.honeybadger.io/blog/python-instantiation-metaclass/) |
| Shard | 79 (laksa) |
| Root Hash | 12035788063718406279 |
| Unparsed URL | com,towardsdatascience!/advanced-python-metaclasses-e32d46e0ebe3/ s443 |