This walks through the full lifecycle of a metaclass= keyword argument, from
the bytecode the compiler emits for a class statement down to the metaclass
resolution logic in builtins.__build_class__ and
_PyType_CalculateMetaclass. All permalinks pin to commit
d931725bc850cd096f6703bc285e885f1e015f05.
metaclass= has no dedicated syntax or opcode. The compiler treats it as a
plain keyword argument to a synthetic call. codegen_class_body in
Python/codegen.c documents the shape of the lowered call at the top of the
function:
/* ultimately generate code for: <name> = __build_class__(<func>, <name>, *<bases>, **<keywords>) where: <func> is a zero arg function/closure created from the class body. It mutates its locals to build the class namespace. <name> is the class name <bases> is the positional arguments and *varargs argument <keywords> is the keyword arguments and **kwds argument This borrows from codegen_call. */
The compiler then pushes LOAD_BUILD_CLASS and the class body closure onto
the stack:
/* 2. load the 'build_class' function */ // these instructions should be attributed to the class line, // not a decorator line loc = LOC(s); ADDOP(c, loc, LOAD_BUILD_CLASS); ADDOP(c, loc, PUSH_NULL);
So class Foo(Base, metaclass=Meta, extra=1): ... is compiled to the moral
equivalent of Foo = __build_class__(<body_func>, 'Foo', Base, metaclass=Meta, extra=1). metaclass is not special to the bytecode; it rides inside the
ordinary **kwds.
The implementation lives at Python/bltinmodule.c in
builtin___build_class__. Its header summarises the contract:
__build_class__(func, name, /, *bases, [metaclass], **kwds) -> class
The original base tuple is preserved, then rewritten via update_bases so
that any non-type bases (e.g. typing.Generic[T]) get a chance to contribute
real types through __mro_entries__. This matters for metaclass resolution,
because the metaclass of a base is computed from the rewritten bases:
orig_bases = PyTuple_FromArray(args + 2, nargs - 2); if (orig_bases == NULL) return NULL; bases = update_bases(orig_bases, args + 2, nargs - 2);
update_bases walks each positional base; for anything that isn't a
PyType_Check and that exposes __mro_entries__, it calls
base.__mro_entries__(bases) and splices the returned tuple in place.
If any replacement happened, __orig_bases__ is later attached to the
namespace (see 2e).
__build_class__ converts the kwnames stack slice into a dict (mkw) and
then removes the metaclass key from it. This is why metaclass= never
reaches __prepare__ or the metaclass' __init__ as a keyword argument
(it's consumed here), while every other class keyword does get forwarded
unchanged.
if (kwnames == NULL) { meta = NULL; mkw = NULL; } else { mkw = _PyStack_AsDict(args + nargs, kwnames); if (mkw == NULL) { goto error; } if (PyDict_Pop(mkw, &_Py_ID(metaclass), &meta) < 0) { goto error; } if (meta != NULL) { /* metaclass is explicitly given, check if it's indeed a class */ isclass = PyType_Check(meta); } }
Two things worth noting:
- The
isclassflag records whether the user-supplied metaclass is itself a class —PyType_Check(meta)isPyType_FastSubclass(Py_TYPE(meta), Py_TPFLAGS_TYPE_SUBCLASS), i.e. it asks whethermeta's metatype is a subclass oftype, which is the C-level spelling ofisinstance(meta, type). Soisclassis true exactly whenmetais a class (regardless of whether its own metaclass istypeor some subclass of it). A non-type callable (e.g. a plain function) fails this check and is handled differently below. metaclassis looked up by the interned identifier&_Py_ID(metaclass), so the key match is exact — you can't smuggle it through as e.g. a string with the same value.
If no explicit metaclass is given, CPython synthesises one:
if (meta == NULL) { /* if there are no bases, use type: */ if (PyTuple_GET_SIZE(bases) == 0) { meta = (PyObject *) (&PyType_Type); } /* else get the type of the first base */ else { PyObject *base0 = PyTuple_GET_ITEM(bases, 0); meta = (PyObject *)Py_TYPE(base0); } Py_INCREF(meta); isclass = 1; /* meta is really a class */ }
Rules:
- No bases →
type. - Otherwise →
type(bases[0]). Only the first base seeds the starting metaclass; the others participate through the conflict check below.
Because the default is always a real type, isclass is forced to 1 in
this branch.
When isclass is true (either defaulted, or user-supplied and passing
isinstance(meta, type)),
__build_class__ asks _PyType_CalculateMetaclass for the most-derived
metatype that is compatible with every base:
if (isclass) { /* meta is really a class, so check for a more derived metaclass, or possible metaclass conflicts: */ winner = (PyObject *)_PyType_CalculateMetaclass((PyTypeObject *)meta, bases); if (winner == NULL) { goto error; } if (winner != meta) { Py_SETREF(meta, Py_NewRef(winner)); } } /* else: meta is not a class, so we cannot do the metaclass calculation, so we will use the explicitly given object as it is */
Critical consequence: if you pass a non-type callable as metaclass=
(e.g. a function, or collections.namedtuple-style factory), CPython
skips the derivation check entirely and uses your callable as-is. This is
the officially-supported escape hatch for "metaclass as factory function".
The resolver itself is just a pairwise subtype walk — the explicit metaclass must be a (non-strict) subtype of every base's metatype, or some base's metatype must be a subtype of it (in which case that base's metatype "wins" and is used instead):
nbases = PyTuple_GET_SIZE(bases); winner = metatype; for (i = 0; i < nbases; i++) { tmp = PyTuple_GET_ITEM(bases, i); tmptype = Py_TYPE(tmp); if (PyType_IsSubtype(winner, tmptype)) continue; if (PyType_IsSubtype(tmptype, winner)) { winner = tmptype; continue; } /* else: */ PyErr_SetString(PyExc_TypeError, "metaclass conflict: " "the metaclass of a derived class " "must be a (non-strict) subclass " "of the metaclasses of all its bases"); return NULL; } return winner;
This is where you get the familiar TypeError: metaclass conflict: ... for
the classic "inherit from two classes with incompatible metaclasses"
scenario.
Once a metaclass is chosen, __build_class__ asks it for a namespace via
__prepare__(name, bases, **remaining_kwds). If the metaclass doesn't
define __prepare__, a plain dict is used. The remaining keyword
arguments (everything except metaclass) are forwarded:
if (PyObject_GetOptionalAttr(meta, &_Py_ID(__prepare__), &prep) < 0) { ns = NULL; } else if (prep == NULL) { ns = PyDict_New(); } else { PyObject *pargs[2] = {name, bases}; ns = PyObject_VectorcallDict(prep, pargs, 2, mkw); Py_DECREF(prep); }
The result is required to pass PyMapping_Check; otherwise a TypeError
mentioning the metaclass name (or the literal string <metaclass> for
non-type metaclasses) is raised:
The class body closure is then executed with that mapping as its locals,
after which the metaclass itself is called as meta(name, bases, ns, **remaining_kwds). If PEP 560 rewrote the bases, __orig_bases__ is
stashed into the namespace first so the metaclass can see the un-rewritten
tuple:
cell = _PyEval_Vector(tstate, (PyFunctionObject *)func, ns, NULL, 0, NULL); if (cell != NULL) { if (bases != orig_bases) { if (PyMapping_SetItemString(ns, "__orig_bases__", orig_bases) < 0) { goto error; } } PyObject *margs[3] = {name, bases, ns}; cls = PyObject_VectorcallDict(meta, margs, 3, mkw);
Note that the metaclass receives the rewritten bases as its second
positional argument, while __orig_bases__ carries the original tuple.
Finally, if any method in the body referenced __class__ (or used
super() without arguments), the compiler will have produced a cell.
__build_class__ cross-checks that the metaclass actually stored the
freshly-built class into that cell — typically by type.__new__ honouring
__classcell__ in the namespace. If a custom metaclass forgets to
propagate __classcell__ you get the classic runtime error:
if (cls != NULL && PyType_Check(cls) && PyCell_Check(cell)) { PyObject *cell_cls = PyCell_GetRef((PyCellObject *)cell); if (cell_cls != cls) { if (cell_cls == NULL) { const char *msg = "__class__ not set defining %.200R as %.200R. " "Was __classcell__ propagated to type.__new__?"; PyErr_Format(PyExc_RuntimeError, msg, name, cls); } else { const char *msg = "__class__ set to %.200R defining %.200R as %.200R"; PyErr_Format(PyExc_TypeError, msg, cell_cls, name, cls); }
This check only fires if cls is itself a type instance — non-type
metaclasses are exempt, which is why a factory-function metaclass can
return whatever it wants.
Putting the above together, the rules a Python programmer actually sees:
metaclass=is an ordinary class-statement keyword argument that the compiler passes through to__build_class__.__build_class__pops it from the keyword dict before anything else looks at the kwargs, so__prepare__,__new__, and__init__never seemetaclass=— only the remaining keywords.- If not supplied: defaults to
typewith zero bases, otherwise totype(bases[0])— and always participates in the subtype check. - If supplied and it's a
typesubclass: CPython picks the most-derived metaclass across the explicit value andtype(b)for every baseb. Pairs that are incomparable raiseTypeError: metaclass conflict: .... - If supplied and it's not a type (e.g. a function): it's used
verbatim; no conflict check, no
__classcell__check. - PEP 560 base rewriting (
__mro_entries__) happens before metaclass resolution, so metaclass derivation is computed against the rewritten bases. The original tuple is re-exposed as__orig_bases__on the namespace. - The chosen metaclass's
__prepare__(if any) builds the namespace and receives the surviving keyword arguments; the class body runs against that namespace; thenmetaclass(name, bases, ns, **kwds)is called to produce the class object.