Python metaclass Example: 5 Powerful Use Cases!

Have you ever wondered how Python classes themselves are created? Have you ever heard of metaclass?

Whether you’re customizing class behavior, enforcing coding patterns, or building frameworks, metaclass offer incredible power and flexibility. In this blog, we’ll look into the layers and explore one of Python’s most advanced and intriguing concepts: metaclass.

python metaclass - 5 powerful use cases
Taken from stackoverflow

We’ll start by understanding the difference between a class and a metaclass, along with what Python’s type exactly is. From there, we’ll dive into creating dynamic classes using type, and look into practical, hands-on examples. You’ll learn how to design your own custom metaclass.

By the end of this blog, you’ll not only understand what metaclasses are but also how to use them in your own projects with the help of 5 use-case examples.

Let’s get started!

metaclass in Python

How metaclass is different from class in Python?

Class in Python defines how an object of that class will look like when they are instantiated. Class is used to define the behavior and data of individual objects.

metaclass in Python defines how a class will work when they are created. They are used to customize classes.

Objects are instances of class, but the classes are instances of those metaclass in Python.

Remember everything in Python is an object? And that is what this means. Even the classes are objects coming from some other class. We all have tried to check the type of variables, right?

a = 20
print(type(a))

Output
<class 'int'>

We expected this, right?

But have you ever tried to look to what is the type of int, str and other types in Python?

print(type(int))

Output
<class 'type'>

It is <class 'type'>… What does that mean?

It means the int is of type class. What is type?

If you are beginner in Python, you might be shocked to see this… type is a metaclass in Python that every class in Python automatically gets as it’s metaclass.

Yes… Every class has metaclass, even if you never mention it. And it looks something like this –

class A(metaclass=type):
    pass

Now, what is the type of type? It is <class 'type'> itself.

So, let’s simplify the things with a simple diagram.

every class has metaclass type

Creating Dynamic Class Using `type`

Now we understood the type in depth, let’s see what we can use it for.

type can be used to create dynamic classes given that you want to build classes on the fly with some base classes that you want it to inherit and some attributes and methods.

Why would you need to create dynamic classes?

If you are building a platform that supports dynamic forms (Users can create their own forms with different fields) then you have to create tables on the database on the fly to cater those use cases as you don’t know beforehand what type of forms with what type of fields they are going to create.

You will have some data that defines that form like the following –

sample_form_data = {
    "form_id": "form_1", 
    "fields": [
        "field1", 
        "field2", 
        "field3"
    ], 
    "field1": {
        "id": "field1", 
        "type": "text", 
        "name": "Field 01", 
    }, 
    "field2": {
        "id": "field2", 
        "type": "text", 
        "name": "Field 02", 
    }, 
    "field3": {
        "id": "field3", 
        "type": "text", 
        "name": "Field 03", 
    }
}

You can create columns based on the fields, map the field types to proper db types, convert them to sql columns and list down all the columns.

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column
from sqlalchemy.dialects.postgresql import VARCHAR

Base = declarative_base()

class DictMixin:
    def __setitem__(self, key, value):
        setattr(self, key, value)
        
    def __getitem__(self, key):
        return getattr(self, key)

def generate_columns(fields):
    columns = []
    for field in fields:
        columns.append(get_column(field))
    return columns

DB_TYPES = {
    'text': VARCHAR
}

def get_column(field):
    field_type = DB_TYPES[field['type']]
    column = Column(
        field_type, 
        name=field['name']
    )
    return column

form_columns = generate_columns(sample_form_data['fields'])

Now creating the dynamic class is left… For that you have to know the syntax of type.

type(name, bases, dict)name is the class name, bases is a tuple of all classes that this class will inherit and dict contains all the attributes this class will have.

So, let’s create the dynamic table for this form-

form_table = type(sample_form_data['form_id'], (Base, DictMixin), {'__tablename__': sample_form_data['form_id'], **form_columns})

Cool, right?

Now, you know how type works and how you can create classes on the fly with it. Let’s dive into how you can create your metaclass.

Create your own metaclass

To create your own metaclass, you just create another class which inherits `type` because as we know this is the defined metaclass in Python.

Then you just create another class and do the following –

class MetaClass(type):
    pass

class A(metaclass=MetaClass):
    pass

Fairly simple code but could be really useful depending on the use cases.

metaclass becomes useful when you want to customize the classes generated from this metaclass.

For that you need to be clear about the concept that metaclass creates the class, which means – when you write a class, it is created from metaclass.

What do I mean by that?

class MetaClass(type):
    def __new__(cls, name, bases, dict):
        print(cls, name)
        return super().__new__(cls, name, bases, dict)
    
class Base(metaclass=MetaClass):
    pass

class Child(Base):
    pass
    
Output
<class '__main__.MetaClass'> Base
<class '__main__.MetaClass'> Child

In this example, you can see that the output comes 2 times. One for Base and other for Child.

__new__ is a special method in Python class which is invoked when an object is created out of the class and __init__ is called after that for object initialization.

We are seeing 2 messages for Base and Child as 2 objects were created from the MetaClass class.

Now, you know how to create your own metaclass. Let’s dive deeper into multiple use cases where metaclasses could be useful.

Python metaclass use-cases

There are multiple use-cases where metaclass in Python could be useful.

Enforcing Class-Level Constraints

Sometimes you might want to make sure that the classes follow certain rules while being implemented.

You might have seen this error multiple times if you work with some plugin, or some libraries.

They make sure that some functions are implemented in the class, and if you don’t implement them, then they throw error.

This kind of mechanism could be achieved by metaclass.

Here you are building a plugin system where every plugin class must implement a run method.

class PluginMeta(type):
    def __new__(cls, name, bases, dict):
        if 'run' in dict.keys():
            raise TypeError(f"Class {name} must implement 'run' method.")
        
        return super().__new__(cls, name, bases, dict)
    
class BasePlugin(metaclass=PluginMeta):
    pass

class MyPlugin(BasePlugin): # Runs fine
    def run(self):
        print("Running MyPlugin")
        
class BrokenPlugin(BasePlugin): # Throws error
    pass
    
Output
TypeError: Class MyPlugin must implement 'run' method.

Automatically Register Subclasses

If you want to keep track of all the classes that are deriving your base class then you can use metaclass.

Here, you are building a component that needs to maintain all available components except BaseComponent.

class RegistryMeta(type):
    registry = {}

    def __new__(cls, name, bases, dct):
        new_class = super().__new__(cls, name, bases, dct)
        if name != 'BaseComponent':  # Avoid registering the base class
            cls.registry[name] = new_class
        return new_class

class BaseComponent(metaclass=RegistryMeta):
    pass

class ComponentA(BaseComponent):
    pass

class ComponentB(BaseComponent):
    pass

print(RegistryMeta.registry)

Output
Output: {'ComponentA': <class '__main__.ComponentA'>, 'ComponentB': <class '__main__.ComponentB'>}

Dynamic Customization

Let’s say you want to make sure that all the classes contain a particular method, then you can create your own metaclass that contains that method and use this metaclass on every class where you want that method.

Here you can include to_dict to all classes of your API.

class AutoDictMeta(type):
    def __new__(cls, name, bases, dct):
        def to_dict(self):
            return {key: value for key, value in self.__dict__.items()}

        dct['to_dict'] = to_dict
        return super().__new__(cls, name, bases, dct)

class APIBase(metaclass=AutoDictMeta):
    pass

class User(APIBase):
    def __init__(self, username, email):
        self.username = username
        self.email = email

u = User("johndoe", "johndoe@example.com")
print(u.to_dict())

Output 
{'username': 'johndoe', 'email': 'johndoe@example.com'}

Singleton Pattern

Sometimes you might want to make sure that there is only one instance of your class and user can’t make any more instances of that class after the first one.

This is called singleton pattern, and we can achieve it using metaclass.

Here you are building a global configuration class that needs to be instantiated only once.

class SingletonMeta(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]

class Config(metaclass=SingletonMeta):
    def __init__(self, value):
        self.value = value

c1 = Config("first")
c2 = Config("second")

print(c1.value)
print(c2.value)
print(c1 is c2)

Output
first
first
True

Adding Debug Information

If you want to debug your code and want to show some message before you run any class related method or attribute, you can use metaclass to log all the creation of your classes for debugging purpose.

Here you can track all class definitions in your application.

class DebugMeta(type):
    def __new__(cls, name, bases, dct):
        print(f"Creating class {name} with attributes {list(dct.keys())}")
        return super().__new__(cls, name, bases, dct)

class DebugBase(metaclass=DebugMeta):
    pass

class Example(DebugBase):
    foo = 42
    def bar(self):
        pass
        
Output
Creating class Example with attributes ['__module__', '__qualname__', 'foo', 'bar']

Conclusion

As Peter Norvig famously said, “Metaclasses are deeper magic than 99% of users should ever need. If you wonder whether you need them, you don’t.

This encapsulates the essence of metaclass: they are a powerful, yet niche tool, designed for scenarios where you need to customize or control class creation in ways standard inheritance and decorators cannot achieve.

metaclass shine when building frameworks, enforcing patterns, or generating dynamic and complex class hierarchies. They help you to abstract away repetitive class behaviors, making your codebase cleaner and more maintainable.

Want to dive deep into other Python concepts, but don’t know which one to start? Read this blog where I discuss about the essential topics of Python that you must master.

Happy Coding 🖥️

References:

Hi, I’m Arup—a full-stack engineer at Enegma and a blogger sharing my learnings. I write about coding tips, lessons from my mistakes, and how I’m improving in both work and life. If you’re into coding, personal growth, or finding ways to level up in life, my blog is for you. Read my blogs for relatable stories and actionable steps to inspire your own journey. Let’s grow and succeed together! 🚀

Leave a Reply

Your email address will not be published. Required fields are marked *