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
- Click CLI Framework
Building CLI applications with Click in Python - FastAPI with OpenAPI
FastAPI with automatic OpenAPI documentation using Pydantic models and … - Flask Essentials
Flask web framework essentials for building web applications and APIs. … - Function Timing Decorator
Decorator to measure function execution time - LangChain Chatbot with Tools
Simple stdin chatbot using LangChain with tool calling (OpenRouter). … - Pandas DataFrames Essential Patterns
Essential patterns for working with Pandas DataFrames: creation, manipulation, … - Pydantic Data Validation
Pydantic - Data validation using Python type hints. Installation 1pip install … - Python Dataclasses
Python dataclasses for clean, boilerplate-free data structures. Basic Usage … - Python Virtual Environments
Managing Python virtual environments and dependencies - Random Forests in Depth
Comprehensive guide to Random Forests: theory, implementation, tuning, and … - Scikit-learn Common Patterns
Common patterns and workflows for scikit-learn: preprocessing, model training, …