Metaclasses
A metaclass is the class of a class. Every class you define is an instance of
type, and type is what actually builds it — a metaclass is a subclass of
type that gets to intervene in that construction. Interviewers ask about
them to find out whether you know when not to use one, which is nearly
always.
The mechanism
class Foo: ... is sugar for a call. Python evaluates the class body into a
namespace dict, then calls the metaclass with the name, the bases and that
dict:
def make(name, bases, ns):
return type(name, bases, ns)
# These two are the same object shape.
class A:
x = 1
B = type("B", (), {"x": 1})
print(type(A), type(B)) # <class 'type'> <class 'type'>Hooking the call
A custom metaclass hooks that call. __new__ runs before the class exists and
can rewrite the namespace; __init__ runs after; __call__ runs every time
the class is called, which is how you intercept instance creation.
class Registry(type):
plugins: dict[str, type] = {}
def __new__(mcls, name, bases, ns):
cls = super().__new__(mcls, name, bases, ns)
if not ns.get("abstract", False):
Registry.plugins[name.lower()] = cls # runs at class definition
return cls
class Plugin(metaclass=Registry):
abstract = True
class CsvExport(Plugin): ...
class JsonExport(Plugin): ...
print(Registry.plugins) # {'csvexport': ..., 'jsonexport': ...}The point to make out loud: the registration happened at import time, when the class statement ran, with no instance involved. That is what a metaclass buys — code that runs when a class is defined.
When you genuinely need one
- Collecting declared fields — the ORM pattern. Django models and old
Pydantic walk the class namespace at definition to find
Fieldobjects and build the schema. - Enforcing rules at definition time — every subclass must define
handle(), or a class attribute must have a certain type. Failing at import beats failing on first use. - Changing what
isinstancemeans —__instancecheck__and__subclasscheck__live on the metaclass.abc.ABCMetais the standard example, and it is why you can register a virtual subclass. - Singletons via
__call__— a metaclass that caches instances. Usually the wrong tool (a module-level instance is simpler), but it is the canonical demo.
Why you almost never need one
__init_subclass__ covers the common cases without a metaclass, and it is
what to reach for first:
class Plugin:
plugins: dict[str, type] = {}
def __init_subclass__(cls, *, abstract: bool = False, **kw):
super().__init_subclass__(**kw)
if not abstract:
Plugin.plugins[cls.__name__.lower()] = cls
class CsvExport(Plugin): ...
class Base(Plugin, abstract=True): ...Same registry, no metaclass, and — the real reason — no metaclass conflicts. Two bases with different metaclasses cannot be combined unless one metaclass subclasses the other, and that error surfaces in whoever inherits from your class later, in a different codebase. A class decorator is the other lightweight option when you only need to touch the finished class.
The order of preference in an interview answer: class decorator or
__init_subclass__ for nearly everything; a metaclass only when you must
change instance creation (__call__), isinstance semantics, or rewrite the
namespace before the class exists.
Where it breaks in production
- Metaclass conflict on multiple inheritance.
TypeError: metaclass conflictwhen a class inherits from two bases with unrelated metaclasses. The fix is a metaclass that subclasses both, which nobody wants to maintain. - Surprise at import time. Registration or validation that runs on import means importing the module has side effects, and a circular import now fails in the metaclass rather than somewhere readable.
- Tooling goes blind. Type checkers and IDEs reason poorly about attributes
a metaclass injects. Pydantic v2 moved to
__init_subclass__and a dataclass transform partly for this reason. __call__override hides construction. A caching__call__meansFoo()sometimes does not construct anything, which reads like a bug to the next engineer.
Related
Interview angle 4
- “What is a metaclass?” - the class of a class.
typeis the default; a custom metaclass customises class creation itself, running at class-definition time rather than instance creation. - “When do you actually need one?” - almost never in application code.
Registering subclasses, enforcing rules at definition, ORM field collection
and custom
isinstanceare the legitimate uses - and__init_subclass__or a class decorator handles the first three more simply. - “
__init_subclass__or a metaclass?” -__init_subclass__. It covers most subclass customisation without metaclass conflicts under multiple inheritance, which is the failure that shows up in someone else’s codebase later. Follow-up: what can only a metaclass do? Override__call__to intercept instance creation, and__instancecheck__to change whatisinstancemeans. - “Show me the class statement as a function call.” -
type(name, bases, namespace). Being able to write that from memory is what convinces an interviewer you understand the mechanism rather than the vocabulary.