ORMs, enums, ABCs and plugin registries customise class creation; you need this to read or build them.
Classes are objects too, and their type is their metaclass — type by default. type(name, bases, namespace) creates a class dynamically, which is exactly what a class statement does under the hood:
- determine the metaclass (from
metaclass=or the bases) - call
Meta.__prepare__(name, bases)to get the namespace mapping - execute the class body in that namespace
- call
Meta(name, bases, namespace), i.e.Meta.__new__thenMeta.__init__ - call
__set_name__on attributes that define it, then__init_subclass__on the parent - apply class decorators
A metaclass can intercept any of these steps, and its methods apply to its classes the way normal methods apply to instances — Enum uses EnumType.__iter__ so you can loop over an enum class, and ABCMeta blocks instantiation of abstract classes.
Before reaching for one, try the lighter hooks, which cover the vast majority of real uses:
__init_subclass__(cls, **kwargs)on a base class runs for every subclass — registries, validation, keyword configuration (class Csv(Exporter, ext="csv"))__set_name__lets descriptors learn their attribute names- class decorators post-process a class (like
@dataclass)
Metaclasses also don't compose well: a class can have only one, and combining bases with unrelated metaclasses raises a "metaclass conflict" TypeError. Use them when you must control the namespace (__prepare__), change how the class object itself behaves (operators or iteration on the class), or intercept instance creation via __call__. As Tim Peters put it, if you wonder whether you need them, you don't.
Example
class Exporter:
registry: dict[str, type] = {}
def __init_subclass__(cls, *, ext: str, **kwargs):
super().__init_subclass__(**kwargs)
cls.ext = ext
Exporter.registry[ext] = cls
class CsvExporter(Exporter, ext="csv"): pass
class JsonExporter(Exporter, ext="json"): pass
print(sorted(Exporter.registry)) # ['csv', 'json']
class Singleton(type): # a metaclass: controls instance creation
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Config(metaclass=Singleton):
def __init__(self):
print("loading config once")
print(Config() is Config()) # loading config once / True
print(type(Config).__name__, type(type).__name__) # Singleton type
Point = type("Point", (), {"x": 0}) # what a class statement does
print(Point.__name__, Point().x) # Point 0Remember
- Classes are instances of their metaclass,
typeby default - A class statement calls
__prepare__, runs the body, thenMeta(name, bases, ns) - Prefer
__init_subclass__,__set_name__or class decorators when they suffice
Watch out for
- Using a metaclass for something
__init_subclass__or a decorator can do, creating metaclass conflicts later.