Metaclasses
A metaclass is the class of a class — it controls how classes themselves are created, letting you customize class construction the same way a class customizes instance construction.
In Python, everything is an object — including classes. Every class is itself an instance of some other object called its metaclass. Understanding metaclasses means understanding one more level of the object model: just as a class is a blueprint that produces instances, a metaclass is a blueprint that produces classes.
Classes Are Objects Too
In Python, a class is not a special compile-time construct like in some statically typed languages — it is a first-class runtime object. You can assign it to a variable, pass it to a function, store it in a list, or inspect its type just like any other value.
The instance of Dog (a Dog object) has type Dog. But Dog itself also has a type — and that type is type. This is the entry point into metaclasses: type is the default metaclass for every class in Python.
class Dog:
def bark(self):
return "Woof!"
print(type(Dog)) # <class 'type'>
print(type(Dog())) # <class '__main__.Dog'>
print(isinstance(Dog, type)) # Truetype: The Built-in Metaclass
type is normally used with one argument to ask for an object's type (type(42) is int). But type also has a three-argument form that creates a brand-new class dynamically: type(name, bases, namespace).
The class statement is syntactic sugar: Python builds the name, the tuple of base classes, and a namespace dict of the class body, then calls a metaclass (type, by default) with those three arguments to produce the class object. A metaclass is simply a way to hook into that final call and customize it.
# These two definitions are equivalent
class Dog:
species = "Canis familiaris"
def bark(self):
return "Woof!"
Dog2 = type(
"Dog2",
(object,),
{"species": "Canis familiaris", "bark": lambda self: "Woof!"},
)
print(Dog2().bark()) # Woof!
print(Dog2.species) # Canis familiarisDefining a Custom Metaclass
A metaclass is a class that inherits from type (directly or indirectly). You attach it to a class with the metaclass= keyword argument in the class header. Whenever that class (or any of its subclasses, by default) is created, your metaclass's machinery runs instead of plain type's.
By convention the first parameter of metaclass methods is named mcs (or cls) rather than self, to signal 'this parameter is the metaclass, and it is being used to build a class' — but it is just a naming convention, not a language rule.
class LoudMeta(type):
def __new__(mcs, name, bases, namespace):
# Uppercase every non-dunder attribute name at class-creation time
upper_namespace = {
(key.upper() if not key.startswith("__") else key): value
for key, value in namespace.items()
}
return super().__new__(mcs, name, bases, upper_namespace)
class Config(metaclass=LoudMeta):
debug = True
version = "1.0"
print(Config.DEBUG, Config.VERSION) # True 1.0
# print(Config.debug) # AttributeError: no attribute 'debug'__new__, __init__, and __prepare__
A metaclass can hook three distinct points in class creation, mirroring — but one level up from — the instance-creation hooks __new__/__init__ you already know from ordinary classes.
__prepare__ is rarely overridden; its main real-world use was capturing attribute-definition order before Python 3.7 made regular dict ordering a language guarantee. __new__ is where you typically transform the namespace or validate the class being built, since the class object does not exist yet. __init__ is where you act on the already-built class object (e.g. registering it somewhere).
1. __prepare__(mcs, name, bases) -> returns the mapping used to
collect the class body's namespace (default: a plain dict,
but you can return an ordered/specialized mapping)
2. __new__(mcs, name, bases, namespace) -> creates and returns
the class object itself (like __new__ on a normal class,
but producing a class instead of an instance)
3. __init__(cls, name, bases, namespace) -> further initializes
the already-created class object (runs after __new__)class TracingMeta(type):
@classmethod
def __prepare__(mcs, name, bases, **kwargs):
print(f"preparing namespace for {name}")
return {}
def __new__(mcs, name, bases, namespace, **kwargs):
print(f"creating class object {name}")
return super().__new__(mcs, name, bases, namespace)
def __init__(cls, name, bases, namespace, **kwargs):
print(f"initializing class {name}")
super().__init__(name, bases, namespace)
class Widget(metaclass=TracingMeta):
pass
# preparing namespace for Widget
# creating class object Widget
# initializing class WidgetHow Python Chooses a Metaclass
When Python executes a class statement, it determines the metaclass to use with a small set of rules: an explicit metaclass= keyword wins; otherwise Python looks at the metaclasses of all base classes and picks the most derived one (a subclass of all the others); if no bases and no explicit metaclass are given, type is used.
If two base classes have unrelated (non-subclassing) metaclasses, Python raises TypeError: metaclass conflict — you must define a metaclass that is a subclass of both to resolve it.
class Meta(type):
pass
class Base(metaclass=Meta):
pass
class Derived(Base): # inherits Meta automatically — no need to repeat it
pass
print(type(Derived)) # <class '__main__.Meta'>Metaclass Use Cases
Real metaclasses are uncommon in application code, but a handful of patterns genuinely need them because the work must happen at class-creation time, not instance-creation time.
Other common uses: enforcing that certain methods are overridden on every subclass (validation), building an ORM's declarative mapping between class attributes and database columns (Django models, SQLAlchemy's declarative base), and implementing abstract base classes — abc.ABCMeta itself is a metaclass that powers @abstractmethod enforcement.
# 1. Automatic subclass registration (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] = cls
return cls
class Plugin(metaclass=PluginMeta):
pass
class CsvPlugin(Plugin):
pass
print(PluginMeta.registry) # {'CsvPlugin': <class '...CsvPlugin'>}Metaclasses vs. Simpler Alternatives
Because a metaclass's __new__/__init__ run once per class definition, several use cases that look like they need a metaclass can be solved more simply and readably with other tools.
Need Simpler alternative
-------------------------------------- ------------------------------
Run code when a subclass is defined __init_subclass__ hook
Wrap/modify a single class a class decorator
Validate/transform instance attributes descriptors or properties
Add methods dynamically to instances plain composition / mixins# __init_subclass__ often replaces a registration metaclass entirely
class Plugin:
registry: dict[str, type] = {}
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
Plugin.registry[cls.__name__] = cls
class CsvPlugin(Plugin):
pass
print(Plugin.registry) # {'CsvPlugin': <class '...CsvPlugin'>}Why Metaclasses Should Be Used Sparingly
Tim Peters' famous remark applies directly here: 'Metaclasses are deeper magic than 99% of users should ever worry about... If you wonder whether you need them, you don't.' Metaclasses affect every subclass transitively, can silently conflict with other metaclasses (including third-party ones, e.g. from an ORM or GUI framework), and make class-creation behavior invisible at the point where a class is actually used — a maintainer reading class Foo(Bar) has no local clue that a metaclass is quietly rewriting Foo's namespace.
Prefer, in order: plain functions/classes, class decorators, __init_subclass__, descriptors — and reach for a custom metaclass only when the behavior must apply to a whole hierarchy of classes at definition time and nothing simpler can express it.
Mental Model
Picture two parallel factory lines. An ordinary class is a factory that manufactures instances; its __new__/__init__ control instance construction. A metaclass is a factory-of-factories: it manufactures classes, and its __new__/__init__ control class construction. type sits at the top — it is the metaclass of itself and of every class that does not specify otherwise.
The metaclass hierarchy metaclass of type and of every class by default instance of type; blueprint for Dog() instances Dog() instance
Quick Reference
type(obj) -> obj's class
type(name, bases, ns) -> dynamically build a new class
class C(metaclass=M): ... -> use M instead of type to build C
M.__prepare__(name, bases) -> namespace mapping to collect the body into
M.__new__(mcs, n, b, ns) -> creates & returns the class object
M.__init__(cls, n, b, ns) -> further initializes the class object
__init_subclass__ -> lighter-weight alternative for most hook needsKey Takeaways
Every class in Python is itself an object whose type is called its metaclass — type by default.
type(name, bases, namespace) is exactly what the class statement calls under the hood.
A custom metaclass hooks __prepare__, __new__, and __init__ to customize how classes (not instances) are built.
Python selects the most derived metaclass among a class's bases automatically; conflicting metaclasses raise TypeError.
Metaclasses are legitimate for registries, ORMs, and enforcing hierarchy-wide rules, but __init_subclass__ and class decorators solve most of the same problems more simply.
Reach for a metaclass only when the behavior must apply automatically to an entire class hierarchy at definition time.
class Meta(type): def __init__(cls, name, bases, ns): cls.registry.append(cls) # AttributeError if registry not set yet class Meta(type): def __new__(mcs, name, bases, ns): ns.setdefault("registry", []) return super().__new__(mcs, name, bases, ns)
Attributes referenced in __init__ must already exist on the namespace or be inherited; set defaults in __new__ (before the class object exists) or guard with getattr/setdefault.
class A(metaclass=Meta1): pass class B(metaclass=Meta2): pass class C(A, B): pass # TypeError: metaclass conflict class CombinedMeta(Meta1, Meta2): pass class C(A, B, metaclass=CombinedMeta): pass
When combining classes whose metaclasses are unrelated, Python cannot pick one automatically; define a metaclass that subclasses both and pass it explicitly.
class Base: def __init_subclass__(cls, kwargs): super().__init_subclass__(kwargs) print(f"{cls.__name__} defined")
__init_subclass__, added in Python 3.6, covers the common 'do something when a subclass is defined' need without the extra complexity and conflict risk of a metaclass.
What is the metaclass of a plain class that defines no metaclass explicitly, and what is the metaclass of type itself?
type in both cases — type is the default metaclass for ordinary classes, and type(type) is also type (type is its own metaclass, the root of the hierarchy).
What are the three arguments accepted by the three-argument form of type(), and what do they correspond to in a class statement?
name (the class's __name__ string), bases (a tuple of base classes), and namespace (a dict mapping attribute names defined in the class body to their values) — exactly what a class Foo(Bar): ... statement assembles before calling the metaclass.
In what order do __prepare__, __new__, and __init__ run during class creation, and what does each receive or produce?
__prepare__ runs first and returns the mapping used to collect the class body into; __new__ runs next, receiving that populated namespace, and creates/returns the actual class object; __init__ runs last, receiving the already-created class object, to perform further setup.
Why might class Combined(A, B) raise 'TypeError: metaclass conflict', and how do you fix it?
It happens when A and B have unrelated metaclasses (neither is a subclass of the other), so Python cannot determine a single most-derived metaclass automatically; fix it by defining a metaclass that subclasses both and passing it explicitly via metaclass=.
Give two real use cases where a metaclass is a reasonable design choice rather than overkill.
Automatic subclass registration for plugin/handler systems that must be tracked centrally at definition time, and building declarative ORMs/frameworks (like Django models) that transform class-body attributes into a different runtime representation (e.g. database field descriptors) uniformly across an entire class hierarchy.
What lighter-weight alternative to a custom metaclass was added in Python 3.6, and what does it cover?
__init_subclass__, a classmethod hook automatically called whenever a subclass is defined; it covers most 'run code when this hierarchy grows a new class' needs without introducing a separate metaclass or the risk of metaclass conflicts.
Write a metaclass NoMutateMeta that raises TypeError if any class using it tries to reassign an already-defined class attribute after the class has been created (i.e. make the class 'frozen' after definition).
class NoMutateMeta(type): def __setattr__(cls, name, value): if name in cls.__dict__: raise TypeError(f"Cannot reassign attribute {name!r} on frozen class {cls.__name__}") super().__setattr__(name, value) class Config(metaclass=NoMutateMeta): debug = True Config.debug = False # raises TypeError
The metaclass overrides __setattr__ (which governs attribute assignment on the class object itself, since the metaclass is the class's type). It checks whether the name already exists in the class's own __dict__ and, if so, refuses the reassignment; new attribute names are still allowed through the super() call.
What does this print, and why does B also get a greeting attribute even though B's class body is empty?
It prints 'Hello from A' then 'Hello from B'. Because Meta is A's metaclass, B (which subclasses A) inherits Meta automatically, so Meta.__new__ runs again when B is created, injecting a fresh greeting into B's own namespace using B's name.
Write a plain function make_class(name, attrs) that uses type() directly (no class statement) to build and return a new class with the given name and a dict of attributes, inheriting from object.
def make_class(name: str, attrs: dict) -> type: return type(name, (object,), dict(attrs)) Point = make_class("Point", {"x": 0, "y": 0, "describe": lambda self: f"({self.x}, {self.y})"}) p = Point() print(p.describe()) # (0, 0) type(name, bases, namespace) is called directly with the class's name, a one-tuple of base classes, and a dict copy of the supplied attributes, producing a fully functional class object identical in kind to one built with a class statement.
class Meta(type):
def __new__(mcs, name, bases, ns):
ns["greeting"] = f"Hello from {name}"
return super().__new__(mcs, name, bases, ns)
class A(metaclass=Meta):
pass
class B(A):
pass
print(A.greeting)
print(B.greeting)