Use Cases for Metaclasses in Python

Rajesh PetheRajesh Pethe
3 min read

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:

  1. Registration

  2. Refactoring or modifying class attributes

  3. 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 a dict 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.

0
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.