Python Metaclasses

Python metaclasses with visual explanations using Mermaid diagrams.


What are Metaclasses?

Metaclasses are classes that create classes.

In Python, everything is an object, including classes:

$$ \text{type}(\text{instance}) = \text{class} $$

$$ \text{type}(\text{class}) = \text{metaclass} $$


Class Creation Flow


Basic Example

1# Every class is an instance of type
2class MyClass:
3    pass
4
5print(type(MyClass))  # <class 'type'>
6print(type(type))     # <class 'type'>
7
8# type is its own metaclass!

Creating Classes Dynamically

 1# Method 1: Normal class definition
 2class Dog:
 3    def bark(self):
 4        return "Woof!"
 5
 6# Method 2: Using type() directly
 7Dog = type('Dog', (), {'bark': lambda self: "Woof!"})
 8
 9# Both are equivalent!
10dog = Dog()
11print(dog.bark())  # Woof!

Custom Metaclass

 1class Meta(type):
 2    """Custom metaclass"""
 3    
 4    def __new__(mcs, name, bases, attrs):
 5        print(f"Creating class: {name}")
 6        # Modify class before creation
 7        attrs['created_by'] = 'Meta'
 8        return super().__new__(mcs, name, bases, attrs)
 9    
10    def __init__(cls, name, bases, attrs):
11        print(f"Initializing class: {name}")
12        super().__init__(name, bases, attrs)
13    
14    def __call__(cls, *args, **kwargs):
15        print(f"Creating instance of: {cls.__name__}")
16        return super().__call__(*args, **kwargs)
17
18# Use metaclass
19class MyClass(metaclass=Meta):
20    def __init__(self, value):
21        self.value = value
22
23# Output:
24# Creating class: MyClass
25# Initializing class: MyClass
26
27obj = MyClass(42)
28# Output:
29# Creating instance of: MyClass
30
31print(obj.created_by)  # Meta

Metaclass Hierarchy


Practical Example: Singleton

 1class SingletonMeta(type):
 2    """Metaclass that creates a Singleton"""
 3    _instances = {}
 4    
 5    def __call__(cls, *args, **kwargs):
 6        if cls not in cls._instances:
 7            cls._instances[cls] = super().__call__(*args, **kwargs)
 8        return cls._instances[cls]
 9
10class Database(metaclass=SingletonMeta):
11    def __init__(self):
12        print("Connecting to database...")
13        self.connection = "Connected"
14
15# Test
16db1 = Database()  # Connecting to database...
17db2 = Database()  # (no output)
18print(db1 is db2)  # True

Practical Example: Auto-Registration

 1class RegistryMeta(type):
 2    """Automatically register all subclasses"""
 3    registry = {}
 4    
 5    def __new__(mcs, name, bases, attrs):
 6        cls = super().__new__(mcs, name, bases, attrs)
 7        if name != 'Plugin':  # Don't register base class
 8            mcs.registry[name] = cls
 9        return cls
10
11class Plugin(metaclass=RegistryMeta):
12    """Base plugin class"""
13    pass
14
15class AudioPlugin(Plugin):
16    def process(self):
17        return "Processing audio"
18
19class VideoPlugin(Plugin):
20    def process(self):
21        return "Processing video"
22
23# All plugins automatically registered
24print(RegistryMeta.registry)
25# {'AudioPlugin': <class 'AudioPlugin'>, 'VideoPlugin': <class 'VideoPlugin'>}
26
27# Use registry
28for name, plugin_class in RegistryMeta.registry.items():
29    plugin = plugin_class()
30    print(f"{name}: {plugin.process()}")

Practical Example: Validation

 1class ValidatedMeta(type):
 2    """Metaclass that validates class attributes"""
 3    
 4    def __new__(mcs, name, bases, attrs):
 5        # Check for required methods
 6        required_methods = ['validate', 'save']
 7        for method in required_methods:
 8            if method not in attrs:
 9                raise TypeError(f"Class {name} must implement {method}()")
10        
11        return super().__new__(mcs, name, bases, attrs)
12
13class Model(metaclass=ValidatedMeta):
14    def validate(self):
15        pass
16    
17    def save(self):
18        pass
19
20# This works
21class User(Model):
22    def validate(self):
23        return True
24    
25    def save(self):
26        print("Saving user...")
27
28# This fails
29try:
30    class InvalidModel(Model):
31        def validate(self):
32            pass
33        # Missing save() method!
34except TypeError as e:
35    print(e)  # Class InvalidModel must implement save()

Practical Example: ORM-like Behavior

 1class Field:
 2    def __init__(self, field_type):
 3        self.field_type = field_type
 4    
 5    def __set_name__(self, owner, name):
 6        self.name = name
 7    
 8    def __get__(self, obj, objtype=None):
 9        if obj is None:
10            return self
11        return obj.__dict__.get(self.name)
12    
13    def __set__(self, obj, value):
14        if not isinstance(value, self.field_type):
15            raise TypeError(f"{self.name} must be {self.field_type}")
16        obj.__dict__[self.name] = value
17
18class ModelMeta(type):
19    """ORM-like metaclass"""
20    
21    def __new__(mcs, name, bases, attrs):
22        # Collect fields
23        fields = {}
24        for key, value in list(attrs.items()):
25            if isinstance(value, Field):
26                fields[key] = value
27        
28        attrs['_fields'] = fields
29        return super().__new__(mcs, name, bases, attrs)
30
31class Model(metaclass=ModelMeta):
32    def __init__(self, **kwargs):
33        for name, value in kwargs.items():
34            setattr(self, name, value)
35    
36    def to_dict(self):
37        return {name: getattr(self, name) for name in self._fields}
38
39class User(Model):
40    name = Field(str)
41    age = Field(int)
42    email = Field(str)
43
44# Usage
45user = User(name="Alice", age=30, email="alice@example.com")
46print(user.to_dict())
47# {'name': 'Alice', 'age': 30, 'email': 'alice@example.com'}
48
49# Type validation
50try:
51    user.age = "invalid"  # TypeError
52except TypeError as e:
53    print(e)  # age must be <class 'int'>

Method Resolution Order (MRO)

 1class Meta1(type):
 2    def method(cls):
 3        return "Meta1"
 4
 5class Meta2(type):
 6    def method(cls):
 7        return "Meta2"
 8
 9class CombinedMeta(Meta1, Meta2):
10    pass
11
12class MyClass(metaclass=CombinedMeta):
13    pass
14
15# MRO determines which method is called
16print(MyClass.method())  # Meta1
17print(CombinedMeta.__mro__)
18# (<class 'CombinedMeta'>, <class 'Meta1'>, <class 'Meta2'>, <class 'type'>, <class 'object'>)

When to Use Metaclasses

Good use cases:

  • Framework development (Django ORM, SQLAlchemy)
  • Plugin systems
  • API clients with auto-generated methods
  • Validation and type checking
  • Singleton patterns
  • Automatic registration

Avoid for:

  • Simple inheritance (use regular classes)
  • Decorators can solve it (use decorators instead)
  • Class decorators work (simpler alternative)

Quote: "Metaclasses are deeper magic than 99% of users should ever worry about." - Tim Peters


Alternatives to Metaclasses

Class Decorators

 1def singleton(cls):
 2    instances = {}
 3    def get_instance(*args, **kwargs):
 4        if cls not in instances:
 5            instances[cls] = cls(*args, **kwargs)
 6        return instances[cls]
 7    return get_instance
 8
 9@singleton
10class Database:
11    def __init__(self):
12        self.connection = "Connected"
13
14# Simpler than metaclass!

__init_subclass__ (Python 3.6+)

 1class Plugin:
 2    registry = {}
 3    
 4    def __init_subclass__(cls, **kwargs):
 5        super().__init_subclass__(**kwargs)
 6        cls.registry[cls.__name__] = cls
 7
 8class AudioPlugin(Plugin):
 9    pass
10
11class VideoPlugin(Plugin):
12    pass
13
14print(Plugin.registry)
15# {'AudioPlugin': <class 'AudioPlugin'>, 'VideoPlugin': <class 'VideoPlugin'>}

Complete Flow Diagram


Related Snippets