🚀AdvancedMetaprogramming
Metaclasses
Understand how Python creates classes at runtime, write custom metaclasses, and use __init_subclass__ for lightweight hooks.
Overview
In Python, **everything is an object** including classes. A class is an instance of a **metaclass**. The default metaclass is `type`. When Python executes a `class` statement, it calls the metaclass to build the class object.
This means you can intercept class creation by writing a custom metaclass that inherits from `type`. You override `__new__` (called before the class exists) or `__init__` (called after) to inspect or modify the class, enforce constraints, auto-register subclasses, inject methods, or validate attributes.
Modern Python often prefers `__init_subclass__` (PEP 487, Python 3.6+) for many use cases it's a class method that runs when a subclass is created, with far less boilerplate than a full metaclass. `__class_getitem__` lets you support generic-style syntax (`MyClass[int]`).
Metaclasses power many frameworks: Django's ORM models, SQLAlchemy declarative base, Pydantic, and abc.ABCMeta all use metaclasses internally.
Key Points
- •`type(name, bases, namespace)` is the built-in way to create a class dynamically
- •A metaclass is a class whose instances are classes; the default is `type`
- •Set metaclass: `class Foo(metaclass=MyMeta):`
- •Override `__new__(mcs, name, bases, ns)` in the metaclass to modify the class dict before creation
- •Override `__init__(cls, name, bases, ns)` to run code after the class object is created
- •`__init_subclass__(cls, **kwargs)` simpler hook: runs in the parent class when a subclass is created
- •Metaclass conflict: if two base classes have different metaclasses, you must create a combined metaclass
Examples
type() dynamic class creation
# These two definitions are equivalent
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f"Point({self.x}, {self.y})"
# Same using type():
def point_init(self, x, y):
self.x, self.y = x, y
def point_repr(self):
return f"Point({self.x}, {self.y})"
DynPoint = type("DynPoint", (), {
"__init__": point_init,
"__repr__": point_repr,
})
p = DynPoint(3, 4)
print(p) # DynPoint(3, 4)
print(type(p)) # <class '__main__.DynPoint'>
print(type(DynPoint)) # <class 'type'>Metaclass that enforces method naming conventions
class StrictMeta(type):
def __new__(mcs, name, bases, namespace):
for attr, val in namespace.items():
if callable(val) and not attr.startswith('_'):
if not attr.islower():
raise TypeError(
f"{name}.{attr}: public methods must be lowercase"
)
return super().__new__(mcs, name, bases, namespace)
class Good(metaclass=StrictMeta):
def compute(self): pass # ok
try:
class Bad(metaclass=StrictMeta):
def ComputeValue(self): pass # raises TypeError
except TypeError as e:
print(e)__init_subclass__ auto-register plugins
class Plugin:
_registry: dict = {}
def __init_subclass__(cls, name=None, **kwargs):
super().__init_subclass__(**kwargs)
key = name or cls.__name__.lower()
Plugin._registry[key] = cls
class CSVPlugin(Plugin, name="csv"):
pass
class JSONPlugin(Plugin, name="json"):
pass
print(Plugin._registry)
# {'csv': <class 'CSVPlugin'>, 'json': <class 'JSONPlugin'>}Common Mistakes
- Confusing metaclass `__new__` with class `__new__` the metaclass `__new__` creates the *class*, not instances of it
- Forgetting to call `super().__new__(...)` in the metaclass this skips the default class construction
- Using a metaclass when `__init_subclass__` is sufficient metaclasses add complexity; prefer the simpler hook
Note
Ask yourself: "Do I need to control the creation of the class itself?" If yes, use a metaclass. If you just want to react when a subclass is defined, use `__init_subclass__`. If you want to modify instances, use regular `__init__` or decorators.
Exercises
Write Python code to solve each exercise. Run it in your browser and check your output.
Loading editor…
Completed: 0 / 5 exercises
Knowledge Check
Question 1 of 4