Python Metaclasses Explained: A Deep Dive into OOP

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Metaclasses in Python are special classes that create and manage other classes. Since everything in Python is an object—including classes—metaclasses can be thought of as "classes for classes." They provide a mechanism to control the class creation process and modify class behavior at definition time.

How Metaclasses Work

When Python encounters a class definition, it automatically uses the type metaclass to create that class. Here's what happens under the hood:

class MyClass:
    pass

Internally, Python executes:

MyClass = type('MyClass', (), {})

Where:

  • 'MyClass' — the class name
  • () — a tuple of parent classes
  • {} — a dictionary of class attributes and methods

The built-in type metaclass is responsible for creating all classes in Python by default.

Creating Custom Metaclasses

Custom metaclasses are created by inheriting from type and overriding its methods:

class MyMeta(type):
    def __new__(cls, name, bases, attrs):
        print(f"Creating class {name}")
        attrs['created_by_metaclass'] = True
        return super().__new__(cls, name, bases, attrs)

class MyClass(metaclass=MyMeta):
    pass

print(MyClass.created_by_metaclass)  # True

Key Metaclass Methods

__new__

Controls class creation. Called before __init__ and returns the new class:

class CreationMeta(type):
    def __new__(cls, name, bases, attrs):
        # Modify attributes before the class is created
        attrs['class_id'] = id(cls)
        return super().__new__(cls, name, bases, attrs)

__init__

Initializes the newly created class:

class InitMeta(type):
    def __init__(cls, name, bases, attrs):
        print(f"Initializing class {name}")
        super().__init__(name, bases, attrs)

__call__

Defines behavior when class instances are created:

class CallMeta(type):
    def __call__(cls, *args, **kwargs):
        print(f"Creating instance of {cls.__name__}")
        return super().__call__(*args, **kwargs)

Practical Use Cases

Automatic Attribute Transformation

class UpperAttrMeta(type):
    def __new__(cls, name, bases, attrs):
        uppercase_attrs = {}
        for attr_name, attr_value in attrs.items():
            if not attr_name.startswith('__'):
                uppercase_attrs[attr_name.upper()] = attr_value
            else:
                uppercase_attrs[attr_name] = attr_value
        return super().__new__(cls, name, bases, uppercase_attrs)

class MyClass(metaclass=UpperAttrMeta):
    attr1 = 'value1'
    attr2 = 'value2'

print(hasattr(MyClass, 'ATTR1'))  # True
print(hasattr(MyClass, 'attr1'))  # False

Singleton Pattern Implementation

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 DatabaseConnection(metaclass=SingletonMeta):
    def __init__(self):
        print("Creating database connection")

db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2)  # True

Class Structure Validation

class ValidatedMeta(type):
    def __new__(cls, na                

Blogs

Book Recommendations