Python Metaclasses
Control class creation itself using type, __new__, and metaclass hooks
Overview
A metaclass is the "class of a class"—it controls how a class object is constructed, just as a class controls how instances are constructed. The default metaclass is type; customizing it lets you inject attributes, validate definitions, or register subclasses at creation time. Metaclasses are powerful but rarely needed; prefer decorators or __init_subclass__ unless you truly must intercept class creation.
Syntax / Usage
Subclass type and override __new__ or __init__, then attach it with metaclass=.
class Meta(type):
def __new__(mcs, name, bases, namespace, **kwargs):
# namespace holds the class body (methods, attributes)
namespace["created_by"] = "Meta"
cls = super().__new__(mcs, name, bases, namespace)
return cls
class Service(metaclass=Meta):
pass
print(Service.created_by) # 'Meta'
type(name, bases, namespace) can also build a class dynamically without the class keyword.
Examples
Auto-register every subclass in a central registry—useful for plugin systems:
class PluginMeta(type):
registry: dict[str, type] = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
if bases: # skip the base class itself
PluginMeta.registry[name.lower()] = cls
return cls
class Plugin(metaclass=PluginMeta):
pass
class ExportPlugin(Plugin):
pass
print(PluginMeta.registry) # {'exportplugin': <class 'ExportPlugin'>}
Enforce an interface at definition time, failing fast before any instance exists:
class RequiresRun(type):
def __new__(mcs, name, bases, namespace):
if bases and "run" not in namespace:
raise TypeError(f"{name} must define run()")
return super().__new__(mcs, name, bases, namespace)
class Task(metaclass=RequiresRun):
pass
class Job(Task):
def run(self) -> None:
print("working")
Common Mistakes
- Reaching for a metaclass when
__init_subclass__or a class decorator would be simpler and clearer - Mixing incompatible metaclasses via multiple inheritance, triggering a metaclass conflict
TypeError - Confusing
__new__(creates the class object) with__init__(initializes it) - Forgetting to call
super().__new__, so the class is never actually built - Storing mutable shared state on the metaclass without realizing it is shared across all classes
See Also
python-classes python-decorators python-type-hints