🧠 Python DeepCuts — 💡 Metaclasses Made Simple
Posted on: December 3, 2025
Description:
In Python, everything is an object — and that includes classes themselves. Classes are created at runtime, and the mechanism responsible for that creation is called a metaclass.
A metaclass is to a class what a class is to an object. They allow Python to intercept, modify, or enhance class creation, which is how frameworks like Django, SQLAlchemy, Pydantic, Typer, and many ORMs implement:
- automatic class registration
- field generation
- validation rules
- interface enforcement
- introspection hooks
This DeepCut demystifies metaclasses and makes them practical.
🧩 Classes Are Created Using type()
In Python, using class is just syntactic sugar. Under the hood, Python does:
Person = type("Person", (object,), {"x": 42})
This manually creates a class:
Person = type(
"Person", # class name
(object,), # base classes
{"x": 42} # class attributes
)
p = Person()
print(p.x)
type() is literally the class factory.
🧠 new__vs __init — Metaclass Edition
When you define a class using a metaclass, two hooks fire:
- new → creates the class object
- init → configures the class object
class Meta(type):
def __new__(mcls, name, bases, attrs):
print(f"[__new__] Creating class {name}")
cls = super().__new__(mcls, name, bases, attrs)
return cls
def __init__(cls, name, bases, attrs):
print(f"[__init__] Initializing class {name}")
super().__init__(name, bases, attrs)
class Example(metaclass=Meta):
pass
Classes go through two-phase construction — this is where metaclasses inject their logic.
🏗️ Auto-Registration (Used in Frameworks)
Django, Pydantic, and SQLAlchemy often need to automatically track subclasses.
A metaclass can do this:
REGISTRY = {}
class AutoRegister(type):
def __new__(mcls, name, bases, attrs):
cls = super().__new__(mcls, name, bases, attrs)
if name != "Base":
REGISTRY[name] = cls
return cls
class Base(metaclass=AutoRegister):
pass
class User(Base):
pass
class Product(Base):
pass
print(REGISTRY)
This technique powers:
- model discovery
- plugin systems
- automatic routing
- admin panels
- schema generation
🔧 Injecting Attributes Automatically
Metaclasses can modify a class before it exists.
For instance, adding a default str if one is missing:
class AutoStr(type):
def __new__(mcls, name, bases, attrs):
if "__str__" not in attrs:
def __str__(self):
return f"<{name} instance>"
attrs["__str__"] = __str__
return super().__new__(mcls, name, bases, attrs)
class Node(metaclass=AutoStr):
pass
print(Node())
This pattern is used to generate:
- repr
- auto-debugging strings
- dataclass-like helpers
🧬 Enforcing Rules on Class Definitions
Metaclasses can force subclasses to define specific methods or attributes.
Example:
class RequiresSave(type):
def __new__(mcls, name, bases, attrs):
if name != "BaseModel" and "save" not in attrs:
raise TypeError(f"{name} must define save()")
return super().__new__(mcls, name, bases, attrs)
class BaseModel(metaclass=RequiresSave):
pass
class Model(BaseModel):
def save(self):
print("Saving...")
This is how frameworks enforce:
- required fields
- required methods
- interface contracts
- schema completeness
✅ Key Points
- Python creates classes using type(name, bases, attrs)
- Metaclasses let you customize class creation
- new → creation stage
- init → configuration stage
- Frameworks use metaclasses for:
- registration
- validation
- attribute injection
- schema enforcement
Metaclasses give Python the flexibility of dynamic languages with the structure of static frameworks.
Code Snippet:
import inspect
# Create a class manually using type()
Person = type(
"Person", # class name
(object,), # base classes
{"x": 42} # attributes
)
p = Person()
print(Person, p.x)
class Meta(type):
def __new__(mcls, name, bases, attrs):
print(f"[__new__] Creating class {name}")
cls = super().__new__(mcls, name, bases, attrs)
return cls
def __init__(cls, name, bases, attrs):
print(f"[__init__] Initializing class {name}")
super().__init__(name, bases, attrs)
class Example(metaclass=Meta):
pass
REGISTRY = {}
class AutoRegister(type):
def __new__(mcls, name, bases, attrs):
cls = super().__new__(mcls, name, bases, attrs)
if name != "Base":
REGISTRY[name] = cls
return cls
class Base(metaclass=AutoRegister):
pass
class User(Base):
pass
class Product(Base):
pass
print(REGISTRY)
class AutoStr(type):
def __new__(mcls, name, bases, attrs):
if "__str__" not in attrs:
def __str__(self):
return f"<{name} instance>"
attrs["__str__"] = __str__
return super().__new__(mcls, name, bases, attrs)
class Node(metaclass=AutoStr):
pass
n = Node()
print(n)
class RequiresSave(type):
def __new__(mcls, name, bases, attrs):
if name != "BaseModel" and "save" not in attrs:
raise TypeError(f"{name} must define save()")
return super().__new__(mcls, name, bases, attrs)
class BaseModel(metaclass=RequiresSave):
pass
class Model(BaseModel):
def save(self):
print("Saving...")
# Uncommenting this will error:
# class BadModel(BaseModel):
# pass
No comments yet. Be the first to comment!