Use Cases for Metaclasses in Python

Why do we even need metaclasses? Specially when we have class inheritance
and multiple inheritance
and constructors
to granularly define what we need?
Answer is yes! We need metaclass
when we need to influence how a class is defined. In other words - when we need to mutate
a class into doing something special.
While most Python applications might never need to use metaclasses
, here is a list of use cases I can think off:
Registration
Refactoring or modifying class attributes
Wrapping class methods
Registration of classes
models = {}
class ModelMetaclass(type):
def __new__(cls, name, bases, namespace):
cls = type.__new__(cls, name, bases, namespace)
# No need to register `base` class
if name != "Model":
models[name] = cls
return cls
class Model(metaclass=ModelMetaclass):
pass
Any class subclassing Model
, will be registered in the models
dictionary.
class Foo(Model):
pass
class Bar(Model):
pass
print(models.keys())
['Model', 'Foo', 'Bar']
This can be achieved quite easily using class decorators but, imagine using class decorators with every class. So inheritance makes using metaclass
advantageous.
NOTE
A metaclass
's __new__
function gets a set of parameters -
child class, name of the child class.
bases
is a tuple containing all parent classes.child class's
namespace
which is adict
of all attributes and methods defined in class.
A class's namespace
becomes important when you want to alter or use class's attributes and methods.
Refactoring or modifying class attributes
Take a look at django ORM's ModelBase and its __new__
method. It is a metaclass
which is base class for all django models.
Here is an example of doing something similar:
class ModelMetaclass(type):
def __new__(cls, name, bases, namespace):
fields = {}
for key, value in namespace.items():
if isinstance(value, Field):
value.name = f"{name}.{key}"
fields[key] = value
namespace["_fields"] = fields
return type.__new__(meta, name, bases, namespace)
class Model(metaclass=ModelMetaclass):
pass
This adds a field's dot separated name and also adds a _fields
dictionary to namespace
to keep track of all fields. One can iterate over bases
(base classes) and do the same.
Wrapping class methods
This is useful if you need to execute some code or add extra functionality to class methods. And example would be to track run time a long running method or adding debug information.
Here is an example:
from functools import wraps
import time
class ProfilerMeta(type):
def get_wrapper(func, *args, **kwargs):
@wraps(func)
def runtime_wrapper(*args, **kwargs):
start = time.perf_counter()
rv = func(*args, **kwargs)
stop = time.perf_counter()
runtime = stop - start
print(f"Finished executing {func.__name__} in {runtime} seconds")
return rv
return runtime_wrapper
def __new__(cls, name, bases, namespace):
for name, func in list(namespace.items()):
if callable(func):
namespace[name] = cls.get_wrapper(func)
return type.__new__(cls, name, bases, namespace)
class Foo(metaclass=ProfilerMeta):
def long_running_method(self):
time.sleep(5)
print("Finished long_running_method")
def another_long_running_method(self):
time.sleep(3)
print("Finished Another long_running_method")
foo = Foo()
foo.long_running_method()
foo.another_long_running_method()
Output:
Finished long_running_method
Finished executing long_running_method in 5.004354310003691 seconds
Finished Another long_running_method
Finished executing another_long_running_method in 3.003058955000597 seconds
What is the metaclass
ProfilerMeta
doing here is, iterating over namespace
dict of the child class. It then wraps all functions it can find (long_running_method
and another_long_running_method
in this case). It wraps it with a wrapper function runtime_wrapper
which calculates the runtime
while executing the method.
Conclusion
There is no doubt that using metaclasses
is like adding some magic to Python classes, which might make things a little more complex. But there are cases where you just need it to achieve, singleton design pattern is another use case.
Subscribe to my newsletter
Read articles from Rajesh Pethe directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Rajesh Pethe
Rajesh Pethe
I’m a hands-on software engineer with over 18 years of experience solving real-world problems with code. I’ve spent most of that time building web applications, backend systems, and automation tools — often using Python, Django, REST, and a healthy mix of SQL and shell scripts on Linux. Along the way, I’ve also picked up frontend work with Angular and React, and built infrastructure using Docker, Kubernetes, AWS, and Terraform. I wouldn’t call myself a DevOps engineer, but I do believe in owning the full stack — from writing the API to making sure it runs smoothly in production. I’ve worked in all kinds of teams — large, small, remote, distributed, fast-paced, slow-paced. For the last few years, I’ve been freelancing, which has been both freeing and demanding in the best possible way. It’s pushed me to keep learning, stay sharp, and step outside my comfort zone. I love the mix of flexibility and challenge it brings.