Objects/typeobject.c
cpython 3.14 @ ab2d84fe1023/Objects/typeobject.c
The largest file in CPython. PyType_Type is the type object for type objects
themselves. This file implements: type creation (type_new), the C3 MRO
linearization (mro_internal), slot inheritance (inherit_slots,
fixup_slot_dispatchers), descriptor lookup (_PyType_Lookup), attribute
access on types (type_getattro), and the __subclasshook__ /
__instancecheck__ protocol. The slot wrappers that expose C slots as Python
methods (slot_tp_repr, slot_nb_add, etc.) are also here.
Map
| Lines | Symbol | Role | gopy |
|---|---|---|---|
| 50-120 | type_repr | repr(SomeType) via tp_repr. | objects/type.go:typeRepr |
| 121-200 | type_call | type(obj) and type(name, bases, ns) dispatch. | objects/type_call.go:TypeCall |
| 201-300 | type_getattro | Attribute lookup on a type: MRO search then data descriptor check. | objects/type.go:TypeGetAttro |
| 301-380 | type_setattro | Attribute write on a type; invalidates tp_version_tag. | objects/type.go:TypeSetAttro |
| 381-420 | type_traverse, type_clear, type_dealloc | GC traversal, cycle break, and deallocation for type objects. | objects/type.go |
| 421-550 | _PyType_Lookup | MRO attribute search with inline cache keyed on (type, name, version_tag). | objects/type.go:(*Type).Lookup |
| 551-650 | _PyType_LookupId, _PyType_LookupRef | Variants of _PyType_Lookup for C-string keys and borrowed-ref-safe callers. | objects/type.go |
| 651-800 | _PyType_CacheTag, _PyType_Modified | Version-tag management; bump and propagate on mutation. | objects/type.go:Modified |
| 801-950 | type_new_alloc, type_new_set_slots | Allocate the new type object; validate and lay out __slots__. | objects/type.go |
| 951-1100 | type_new_descriptors | Build slot descriptor objects for every __slots__ entry. | objects/type.go:typeNewDescriptors |
| 1101-1200 | type_new_set_names | Walk tp_dict and call __set_name__ on each descriptor. | objects/type.go:typeNewSetNames |
| 1201-1350 | type_new_init_subclass | Call __init_subclass__ on every base passing keyword arguments from the class statement. | objects/type.go:typeNewInitSubclass |
| 1351-2000 | type_new | Full type.__new__(mcs, name, bases, ns, **kw) implementation. | objects/type.go:typeNew |
| 2001-2200 | calculate_metaclass | Select the most-derived metaclass from bases; raise TypeError on conflict. | objects/type.go:calculateMetaclass |
| 2201-2500 | mro_internal, mro_implementation | C3 linearization entry points; return an ordered tuple of types. | objects/type.go:mroInternal |
| 2501-2800 | mro_merge, mro_check_duplicates | Inner C3 merge loop; detects cycles and inconsistencies. | objects/type.go:mroMerge |
| 2801-3200 | type_ready_check_dict, type_ready_fill_dict | Fill tp_dict with wrappers for inherited slots before freezing. | objects/type.go |
| 3201-3800 | type_ready, PyType_Ready | Full type finalization: set tp_base, build MRO, inherit slots, populate tp_dict. | objects/type.go:Ready |
| 3801-4500 | inherit_slots, inherit_special | Copy NULL slot pointers from base to derived type. | objects/type.go:inheritSlots |
| 4501-5000 | fixup_slot_dispatchers, update_all_slots, update_one_slot | Install or refresh slot_* wrapper functions after any class dict mutation. | objects/type.go:fixupSlotDispatchers |
| 5001-6000 | slot_tp_repr, slot_tp_hash, slot_tp_call, slot_tp_str | tp_* wrappers: call the Python __dunder__ via vectorcall. | objects/type.go |
| 6001-7000 | slot_nb_add, slot_nb_subtract, slot_nb_multiply, slot_nb_radd | Numeric slot wrappers; reversed ops try the right-hand type first for subtypes. | objects/type.go |
| 7001-8000 | PyObject_GenericGetDict, PyObject_GenericSetDict, _PyObjectDict_SetItem | Generic instance __dict__ management. | objects/object.go:GenericGetDict |
| 8001-9000 | subtype_setdict, subtype_getweakref, type_get_doc | Subtype-specific slot implementations for __dict__, __weakref__, __doc__. | objects/usertype.go |
| 9001-9800 | super_getattro, super_descr_get, super_init | super() implementation: MRO index search and attribute lookup one slot past the pivot. | objects/super.go |
| 9801-10500 | object_init, object_new, object_repr, object_hash | Slots for object itself (the root base class). | objects/object.go |
| 10501-11000 | type___instancecheck___impl, type___subclasscheck___impl | isinstance / issubclass with __instancecheck__ and __subclasshook__ support. | objects/type.go |
| 11001-11500 | __init_subclass__ default, __class_getitem__ default | Default implementations; __class_getitem__ returns a GenericAlias. | objects/type.go, objects/generic_alias.go |
| 11501-12302 | PyType_AddWatcher, PyType_ClearWatcher, PyType_Watch, PyType_Unwatch | Type watcher API for inline cache invalidation by the specializing interpreter. | objects/type.go:AddWatcher |
Reading
_PyType_Lookup (lines 421 to 550)
cpython 3.14 @ ab2d84fe1023/Objects/typeobject.c#L421-550
The hottest function in the interpreter for attribute-heavy workloads. Walks
tp_mro from left to right, checks each type's tp_dict for name. Before
the walk it probes a global fixed-size hash table (the "lookup cache") keyed
on (type, name). The entry also stores the tp_version_tag of the type at
the time it was cached; a version mismatch forces a full MRO walk and updates
the cache.
PyObject *
_PyType_Lookup(PyTypeObject *type, PyObject *name)
{
PyObject *mro = type->tp_mro;
Py_ssize_t n = PyTuple_GET_SIZE(mro);
for (Py_ssize_t i = 0; i < n; i++) {
PyTypeObject *base = (PyTypeObject *)PyTuple_GET_ITEM(mro, i);
PyObject *res = PyDict_GetItemWithError(base->tp_dict, name);
if (res != NULL) {
return res;
}
}
return NULL;
}
The actual implementation wraps this loop with the version-tag cache check. On
a cache hit the version tag of the cached type is compared with the current
type->tp_version_tag; if they match the cached result is returned without
touching any dict. Cache entries are invalidated whenever tp_version_tag
changes, which happens in _PyType_Modified after any mutation of a type or
its bases.
type_new (lines 1351 to 2000)
cpython 3.14 @ ab2d84fe1023/Objects/typeobject.c#L1351-2000
type(name, bases, namespace) implemented in C. The sequence of operations:
- Validate that
basesis a non-empty tuple of types. - Call
calculate_metaclassto find the most-derived metaclass among all bases. - If the winning metaclass is not
typeitself, forward tometaclass(name, bases, ns)and return. - Allocate the type object via
type_new_alloc, compute layout from__slots__. - Copy entries from the user-supplied namespace into
tp_dict. - Call
type_new_descriptorsto buildmember_descriptorobjects for__slots__. - Call
PyType_Readyto fill inherited slots and build the MRO. - Call
type_new_set_namesto invoke__set_name__(owner, name)on each descriptor that defines it. - Call
type_new_init_subclassto invoke__init_subclass__on each base, forwarding any keyword arguments from the class statement.
calculate_metaclass selects the metaclass:
static PyTypeObject *
calculate_metaclass(PyTypeObject *metatype, PyObject *bases)
{
PyTypeObject *winner = metatype;
Py_ssize_t nbases = PyTuple_GET_SIZE(bases);
for (Py_ssize_t i = 0; i < nbases; i++) {
PyTypeObject *base = (PyTypeObject *)PyTuple_GET_ITEM(bases, i);
PyTypeObject *basemet = Py_TYPE(base);
if (PyType_IsSubtype(winner, basemet))
continue;
if (PyType_IsSubtype(basemet, winner)) {
winner = basemet;
continue;
}
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;
}
MRO: mro_internal (lines 2201 to 2500)
cpython 3.14 @ ab2d84fe1023/Objects/typeobject.c#L2201-2500
C3 linearization. Builds a list of lists: each direct base's existing MRO plus
the bases tuple itself. The merge loop repeatedly picks the head of the first
list whose head does not appear in the tail of any other list:
// Pseudocode for mro_merge
lists = [base.mro for base in bases] + [list(bases)]
result = [type]
while any list is non-empty:
for lst in lists:
candidate = lst[0]
if candidate not in [l[1:] for l in lists]:
result.append(candidate)
for lst in lists:
if lst[0] == candidate:
lst.pop(0)
break
else:
raise TypeError("Cannot create a consistent method resolution order")
If no candidate is found in a full sweep, the bases have an inconsistent
ordering and TypeError is raised. The result is stored as type->tp_mro, a
tuple borrowed by all subsequent _PyType_Lookup calls.
PyType_Ready (lines 3201 to 3800)
cpython 3.14 @ ab2d84fe1023/Objects/typeobject.c#L3201-3800
Called once per statically defined C type at module or interpreter initialization. The critical operations:
- Set
tp_baseto&PyBaseObject_Typeif still NULL. - Call
PyType_Readyrecursively ontp_baseand each element oftp_basesif they are not yet ready. - Compute
tp_mroviamro_internal. - Call
inherit_slotsto copy NULL slot pointers from the base type. - Populate
tp_dictwithslot_wrapperandmethod_descriptorobjects for every slot the type exposes. - Set
Py_TPFLAGS_READYon the type.
After PyType_Ready a C type is immutable from the C side. Python subclassing
goes through type_new instead, which calls PyType_Ready on the newly
created type before returning.
Slot wrappers (lines 5001 to 7000)
cpython 3.14 @ ab2d84fe1023/Objects/typeobject.c#L5001-7000
When a Python class defines __repr__, CPython installs slot_tp_repr into
tp->tp_repr. That C function then calls self.__repr__() via
_PyObject_CallMethodNoArgs. The numeric wrappers are more complex because
they handle reflected operations:
static PyObject *
slot_nb_add(PyObject *self, PyObject *other)
{
PyObject *r;
// If type(other) is a subtype of type(self), try other.__radd__ first.
if (!Py_IS_TYPE(self, Py_TYPE(other)) &&
PyType_IsSubtype(Py_TYPE(other), Py_TYPE(self))) {
r = call_maybe(other, &_Py_ID(__radd__), self);
if (r != Py_NotImplemented)
return r;
Py_DECREF(r);
}
r = call_maybe(self, &_Py_ID(__add__), other);
if (r != Py_NotImplemented)
return r;
Py_DECREF(r);
r = call_maybe(other, &_Py_ID(__radd__), self);
return r;
}
fixup_slot_dispatchers is called whenever a class __dict__ changes: it
walks every slot and (re-)installs the appropriate slot_* function or NULL
depending on whether the corresponding __dunder__ is defined anywhere in the
MRO.
super_getattro (lines 9001 to 9500)
cpython 3.14 @ ab2d84fe1023/Objects/typeobject.c#L9001-9500
super(type, obj).__getattribute__(name) starts the MRO search one entry past
type in obj's MRO. The pivot search:
// Find the index of 'type' in mro(type(obj)):
PyObject *mro = Py_TYPE(obj)->tp_mro;
Py_ssize_t n = PyTuple_GET_SIZE(mro);
Py_ssize_t i;
for (i = 0; i < n; i++) {
if ((PyObject *)type == PyTuple_GET_ITEM(mro, i))
break;
}
// Walk from i+1 onward:
for (i++; i < n; i++) {
PyTypeObject *base = (PyTypeObject *)PyTuple_GET_ITEM(mro, i);
PyObject *res = PyDict_GetItemWithError(base->tp_dict, name);
if (res != NULL) {
descrgetfunc f = Py_TYPE(res)->tp_descr_get;
if (f != NULL)
res = f(res, obj, (PyObject *)Py_TYPE(obj));
return res;
}
}
If the name is found, descriptors are invoked normally. This is the entire
mechanism behind super().method(): no magic beyond an MRO scan starting one
slot after the pivot type.
gopy mirror
objects/type.go, objects/type_call.go, objects/usertype.go,
objects/super.go. _PyType_Lookup maps to (*Type).Lookup. The MRO is a
Go []*Type slice. Slot wrappers are generated by gen/slots/ from
Include/cpython/typeslots.h. PyType_Ready maps to (*Type).Ready.
CPython 3.14 changes
__class_getitem__ protocol stabilized (returns GenericAlias by default).
Type watcher API (PyType_AddWatcher) added in 3.12; improved overflow
handling in 3.13. tp_version_tag overflow handling improved in 3.13.
__init_subclass__ keyword-argument forwarding has been stable since 3.6.
PEP 649 adds __annotations__ lazy evaluation, changing how annotation
descriptors in class bodies interact with type_new.