The type and object Relationship
At the very base of Python’s object system sit two special built-in classes whose relationship looks, at first glance, paradoxical.
objectis the root base class: every class’s method resolution order ends atobject, andobjectitself has no base (object.__bases__is the empty tuple).typeis the root metaclass: every class — includingobject, and includingtypeitself — is an instance oftype, sotype(type) is type. Combine these and you get a closed loop:typeis an instance oftype;objectis an instance oftype; andtypeis a subclass ofobject. No third object is needed to break the circle, and none exists. CPython resolves the apparent chicken-and-egg by hard-wiring the loop in C — partly as static initializers compiled into the binary, partly during interpreter startup in_PyTypes_InitTypes(Objects/object.c, as of CPython 3.14.5). This note untangles that loop, distinguishes the two independent “is-a” axes it lives on, and shows why this exact two-axis structure is what makes metaclasses possible at all.
This note is the companion to Metaclasses. The division of labour: this note owns the two-axis structure and the bootstrap — what object and type are, how the circular references are constructed, and how isinstance/issubclass/type() read the two axes. The sibling owns the class-creation pipeline — what happens when a class statement runs, the metaclass hooks, conflict resolution, and use cases. The note Type Objects and PyTypeObject covers the PyTypeObject C struct itself (the slot table) and explicitly defers the bootstrap story to here.
Mental Model — Two Axes, Not One
The single conceptual mistake that makes the type/object relationship feel paradoxical is collapsing two different relationships into one word, “is-a”. Python objects participate in two independent directed graphs that happen to be drawn over the same set of class objects:
- The instance-of axis — “what is the type of this object?” Every Python object, including every class object, carries a pointer to the class it is an instance of. In CPython this is the
ob_typefield of the object header, read bytype(x). For ordinary values:type(42)isint. For classes:type(int)istype,type(str)istype, and cruciallytype(type)istype. The instance-of axis, followed upward, always lands ontypeand stops there, becausetypeis its own type. - The subclass-of (inheritance) axis — “what does this class inherit from?” Every class carries its bases (
__bases__) and its linearized method resolution order (__mro__). For classes:int.__bases__is(object,),type.__bases__is(object,), andobject.__bases__is()— the empty tuple, becauseobjectis the root and inherits from nothing. The subclass-of axis, followed upward, always lands onobjectand stops there.
These two axes are orthogonal. int is an instance of type (instance-of axis) and inherits from object (subclass-of axis); neither fact implies the other. The reason the relationship feels circular is that object and type each sit at the top of one axis while being an ordinary participant in the other: object is the top of inheritance but is itself an instance of type; type is the top of instance-of but is itself a subclass of object.
graph TD subgraph instanceof ["instance-of axis (ob_type / type(x))"] direction LR u1["MyClass<br/>(a user class)"] -->|"type(MyClass)"| ty1["type"] o1["object"] -->|"type(object)"| ty1 ty1 -->|"type(type) is type"| ty1 end subgraph subclassof ["subclass-of axis (__bases__ / __mro__)"] direction LR u2["MyClass"] -->|"inherits"| o2["object"] ty2["type"] -->|"inherits"| o2 o2 -.->|"__bases__ == ()"| nil["(nothing)"] end
Diagram: the same three class objects (object, type, a user class MyClass) drawn on both axes. Instance-of (top) flows toward type and loops at type; subclass-of (bottom) flows toward object and terminates. The insight: there is no contradiction because the two terminating nodes live on different axes — type terminates instance-of, object terminates subclass-of, and each is a normal node on the other’s axis.
You can confirm the whole structure at a REPL — these are not approximations, they are exact identities the interpreter guarantees:
>>> type(object) # object is an instance of type
<class 'type'>
>>> type(type) # type is an instance of itself — the loop
<class 'type'>
>>> type.__bases__ # type inherits from object
(<class 'object'>,)
>>> object.__bases__ # object inherits from nothing — the root
()
>>> issubclass(type, object)
True
>>> issubclass(object, type)
FalseThe last two lines are worth dwelling on. type is a subclass of object (every class is, since object is the universal base), but object is not a subclass of type — object is merely an instance of type. Mixing those up is the canonical confusion this note exists to dispel.
Mechanical Walk-through — How CPython Bootstraps the Loop
A circular reference cannot be built by ordinary, sequential object construction: to make type an instance of type you would already need type to exist. CPython sidesteps this in two stages, at two different times, using two different mechanisms. Conflating these two stages is the most common error in informal explanations, so we keep them strictly separate.
Stage 1 — Static initializers (compile time)
object and type are not heap-allocated at startup like user classes; they are static PyTypeObject structs defined directly in C, so their storage and their header fields are fixed when the interpreter binary is compiled. The relevant fields are the object header’s type pointer, set by the PyVarObject_HEAD_INIT(type_ptr, size) macro, which writes type_ptr into the struct’s ob_type slot.
In Objects/typeobject.c (CPython 3.14.5), the type class is defined as:
PyTypeObject PyType_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0) // ob_type = &PyType_Type (type's type is type)
"type", // tp_name
sizeof(PyHeapTypeObject), // tp_basicsize
...
0, // tp_base (NULL here — filled in at runtime)
...
type_call, // tp_call (makes `type(...)` callable)
...
type_new, // tp_new (the default metaclass constructor)
};Line-by-line, the load-bearing parts:
PyVarObject_HEAD_INIT(&PyType_Type, 0)— this is the bootstrap’s keystone. It initializesPyType_Type’s own header to point itsob_typeat&PyType_Type— the address of the very struct being defined. Taking the address of a not-yet-fully-initialized static object is legal in C (the storage exists at compile time), so this self-reference compiles cleanly. The runtime consequence istype(type) is type: the instance-of loop is baked into the binary, true before a single line of Python runs."type"/tp_basicsize— the class’s name and instance size; an instance oftypeis a class object, laid out as aPyHeapTypeObject.0, // tp_base— notetype’s base is NULL in the static initializer. The subclass-of link fromtypetoobjectis not set here; it is wired up later, in Stage 2.type_callandtype_new— the C functions that maketypecallable and that construct new classes. These are what turntype(name, bases, dict)and theclassstatement into class objects (the subject of Metaclasses).
The object class, PyBaseObject_Type, is defined the same way:
PyTypeObject PyBaseObject_Type = {
PyVarObject_HEAD_INIT(&PyType_Type, 0) // ob_type = &PyType_Type (object's type is type)
"object", // tp_name
sizeof(PyObject), // tp_basicsize
...
0, // tp_base (NULL — and it stays NULL; object has no base)
...
object_new, // tp_new
};PyVarObject_HEAD_INIT(&PyType_Type, 0)—object’s header points attype, sotype(object) is type. Again, baked in statically.0, // tp_base—object’s base is NULL and, unliketype, it stays NULL forever. This is the C-level encoding of “object.__bases__ == ()”:objectis the root of the inheritance axis, the place every MRO terminates.
So after compilation, before any runtime initialization, the instance-of axis is already complete: object.ob_type == &PyType_Type and type.ob_type == &PyType_Type. What is not yet set is the subclass-of link type → object.
Stage 2 — _PyTypes_InitTypes (interpreter startup)
The subclass-of wiring, the MRO computation, slot inheritance, and the population of each class’s tp_dict happen at runtime, early in interpreter startup, in _PyTypes_InitTypes (Objects/object.c). This function walks a static array static_types[] and calls _PyStaticType_InitBuiltin (which runs PyType_Ready) on each. The array is deliberately ordered so that object and type come first, with a comment that says exactly why:
static PyTypeObject* static_types[] = {
// The two most important base types: must be initialized first and
// deallocated last.
&PyBaseObject_Type, // object
&PyType_Type, // type
// Static types with base=&PyBaseObject_Type
&PyAsyncGen_Type,
... ~150 more ...
};
PyStatus
_PyTypes_InitTypes(PyInterpreterState *interp)
{
for (size_t i=0; i < Py_ARRAY_LENGTH(static_types); i++) {
PyTypeObject *type = static_types[i];
if (_PyStaticType_InitBuiltin(interp, type) < 0) {
return _PyStatus_ERR("Can't initialize builtin type");
}
if (type == &PyType_Type) {
// Sanity checks of the two most important types
assert(PyBaseObject_Type.tp_base == NULL);
assert(PyType_Type.tp_base == &PyBaseObject_Type);
}
}
...
}Reading this carefully:
- The array lists
&PyBaseObject_Type(object) and&PyType_Type(type) first, with the comment “must be initialized first.”PyType_Readyfortypeneedsobjectto be a ready type already, because it will settype’s base toobjectand inherit slots from it — hence the ordering constraint. _PyStaticType_InitBuiltin→PyType_Readyis what fills intp_base, computestp_mro, and inherits slot functions. Fortype, this setsPyType_Type.tp_base = &PyBaseObject_Type(the subclass-of link the static initializer left NULL). Forobject,PyType_Readyleavestp_baseNULL becauseobjectgenuinely has no base.- The two
asserts, run immediately aftertypeis readied, verify the bootstrap invariant:object’s base is NULL (root of inheritance) andtype’s base is nowobject. These assertions are the C source’s own statement of the relationship — primary-source confirmation that, once startup completes,typeis a subclass ofobjectandobjectis the rootless base.
The clean summary: the instance-of loop is a compile-time fact (static HEAD_INIT macros); the subclass-of wiring type → object is a runtime-startup fact (PyType_Ready inside _PyTypes_InitTypes). They are different mechanisms completed at different times, and the asserts mark the moment both are true.
Reading the Two Axes — type(), isinstance, issubclass
Once the structure exists, three built-ins read it, and they read different axes — another frequent source of confusion.
type(x) reads the instance-of axis only: it returns x’s ob_type verbatim, with no walking. It answers “what class was x directly created from?” and ignores inheritance entirely. There is also a three-argument form, type(name, bases, dict), which is not a query at all but the class constructor — covered in Metaclasses. CPython distinguishes the two by argument count inside type_call (Objects/typeobject.c), and the one-argument query form is deliberately restricted to type itself, not inherited by metaclass subclasses (a fix for issue gh-27157):
type_call(PyObject *self, PyObject *args, PyObject *kwds)
{
PyTypeObject *type = PyTypeObject_CAST(self);
...
/* Special case: type(x) should return Py_TYPE(x) */
/* We only want type itself to accept the one-argument form (#27157) */
if (type == &PyType_Type) {
Py_ssize_t nargs = PyTuple_GET_SIZE(args);
if (nargs == 1 && (kwds == NULL || !PyDict_GET_SIZE(kwds))) {
obj = (PyObject *) Py_TYPE(PyTuple_GET_ITEM(args, 0)); // return ob_type directly
return Py_NewRef(obj);
}
if (nargs != 3) {
PyErr_SetString(PyExc_TypeError, "type() takes 1 or 3 arguments");
return NULL;
}
}
...
}- The guard
if (type == &PyType_Type)means the one-arg shortcut fires only when you calltype(...)directly, not when you call a custom metaclassMyMeta(x)— for a metaclass, a one-argument call would be a malformed class definition, so it is not silently reinterpreted as a type query. obj = (PyObject *) Py_TYPE(...)is literally “read theob_typefield”;type(x)is that one pointer dereference.
isinstance(x, C) and issubclass(D, C) read the subclass-of axis (with extensions). isinstance(x, C) is, in the common case, equivalent to issubclass(type(x), C): it takes x’s class and asks whether C is in that class’s MRO. issubclass(D, C) asks whether C appears in D.__mro__. The difference from type() is decisive: type(True) is bool (instance-of is exact, and True’s class is bool, not int), but isinstance(True, int) is True because bool inherits from int and so int is in bool.__mro__. Use type(x) is C when you need an exact class match; use isinstance when subclasses should also qualify — the latter is almost always the right default.
isinstance/issubclass can be overridden
isinstanceandissubclassare not pure MRO walks: a metaclass may define__instancecheck__/__subclasscheck__to override them, which is exactly how Abstract Base Classes makeregister()-ed virtual subclasses and__subclasshook__-matched classes reportTruewithout appearing in the MRO.type(), by contrast, can never be overridden this way — it always returns the rawob_type. This is the deep reason the two are not interchangeable.
Why This Two-Axis Structure Makes Metaclasses Possible
The payoff of the whole arrangement: because “the type of a class” is a real, first-class slot (ob_type) on the instance-of axis — distinct from “the base of a class” on the subclass-of axis — a class object can have a type other than type. That other type is a metaclass: any class whose instances are themselves classes. type is the default and most-derived-common metaclass, but a subclass of type can replace it for a given class. When you write class Foo(Base, metaclass=Meta), you are setting Foo’s instance-of link to Meta while leaving its subclass-of link pointing at Base — the two axes are configured independently, which is only coherent because they are genuinely separate slots.
This is also why type must be its own type and a subclass of object. Being a subclass of object makes type an ordinary class that participates in the data model (it has __repr__, __call__, attribute access). Being an instance of type makes type a class factory — calling it constructs classes. A metaclass Meta(type) inherits both: it is-a class (via object) and it makes classes (via type). Remove either half of the type/object loop and metaclasses collapse — without type being instance-of type, there would be no closed set of “classes that make classes”; without type being subclass-of object, metaclasses could not themselves be manipulated as ordinary objects. The bootstrap loop is not an accident to be tolerated; it is the structural precondition for Python’s uniform “everything is an object, including classes” model.
Common Misunderstandings
“object is the base of type, so object must be ‘more fundamental’ — shouldn’t type be an instance of object?” It is: type is both a subclass of object (inheritance) and an instance of type (instance-of). “More fundamental” is ambiguous because the two axes have different roots. object is the root of inheritance; type is the root of instance-of. Neither is globally “first”; they are co-equal and mutually referential, which is exactly why both must be initialized together at the head of static_types[].
“type(type) is type is an infinite regress / a bug.” No — it is a fixed point, not a regress. Following the instance-of axis from type lands back on type in one step and stays there; there is nothing to recurse into. The C code expresses this as a static self-reference in PyVarObject_HEAD_INIT(&PyType_Type, 0), fully resolved at compile time.
“Every class inherits from type.” Every class is an instance of type (or a metaclass subclass of type); every class inherits from object. Swapping “instance of” and “inherits from” here is the single most common slip. int does not inherit from type — int.__mro__ is (int, object), with no type in it — yet type(int) is type.
“object.__class__ and object.__bases__ should be symmetric.” They sit on different axes. object.__class__ is type (instance-of, non-empty), while object.__bases__ is () (subclass-of, empty). The asymmetry is the whole point: object tops one axis and is an ordinary node on the other.
See Also
- Metaclasses — the sibling note: the class-creation pipeline,
__prepare__/__init_subclass__/__set_name__, conflict resolution, and use cases. Read together with this note. - Type Objects and PyTypeObject — the
PyTypeObjectC struct (the slot table) that bothobjectandtypeare instances of; defers the bootstrap to here. - Method Resolution Order and C3 Linearization — how the subclass-of axis is linearized into
__mro__, the axisissubclasswalks. - Abstract Base Classes —
ABCMetaoverrides__instancecheck__/__subclasscheck__, the mechanism by whichisinstance/issubclassdiverge from a pure MRO walk. - Python Internals MOC §13 — The Type System and Data-Model Protocols.