Open-Source Wikis

/

CPython

/

Systems

/

Object model

python/cpython

Object model

Every Python value in CPython is a pointer to a PyObject. The object's behaviour is supplied by a PyTypeObject ("the type") through C function pointers ("type slots"). This page is a tour of how the model is laid out in memory and where the major built-in types live.

The most relevant InternalDocs are Objects/object_layout.md and the *.gv Graphviz diagrams alongside it.

PyObject layout

struct _object {
    Py_ssize_t   ob_refcnt;      // refcount (or atomic split refcount in nogil builds)
    PyTypeObject *ob_type;       // pointer to the type
};

This is the start of every Python value. Variable-sized objects (tuple, str, bytes, list, …) follow with a Py_ssize_t ob_size:

typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size;
} PyVarObject;

The exact layout depends on the build:

  • Standard build — refcount is a single Py_ssize_t.
  • --with-pydebug — adds a doubly-linked list of all live objects (Py_TRACE_REFS).
  • --disable-gil — split refcounts: a thread-local "owner" count and a shared atomic count, plus an ob_tid field tagging the owning thread. See Include/refcount.h.

Header definitions live in Include/object.h and Include/cpython/object.h.

PyTypeObject and slots

A type is an object whose ob_type is &PyType_Type. It's a struct with a few hundred fields, organised by protocol:

graph TD
    Type[PyTypeObject] --> Repr[tp_repr / tp_str / tp_hash]
    Type --> Lifecycle[tp_alloc / tp_new / tp_init / tp_dealloc]
    Type --> GC[tp_traverse / tp_clear / tp_finalize]
    Type --> Number[tp_as_number]
    Type --> Sequence[tp_as_sequence]
    Type --> Mapping[tp_as_mapping]
    Type --> Buffer[tp_as_buffer]
    Type --> Async[tp_as_async]
    Type --> Iter[tp_iter / tp_iternext]
    Type --> Descr[tp_descr_get / tp_descr_set]
    Type --> Methods[tp_methods / tp_members / tp_getset]
    Type --> MRO[tp_bases / tp_mro / tp_subclasses]

Each tp_as_* is a pointer to a smaller struct of operator-specific slots (nb_add, sq_length, mp_subscript, bf_getbuffer, …). The full layout is in Include/cpython/object.h.

Two flavours of types exist:

  • Static typesstatic PyTypeObject Foo_Type = { ... }; declared in C. Cheap, but shared across all subinterpreters and not garbage-collected.
  • Heap types — created at runtime via PyType_FromModuleAndSpec from a PyType_Spec. Subinterpreter-safe, GC-tracked, support per-module state. All new built-in types must be heap types.

The dispatch from the dot operator to a slot is implemented in Objects/object.c (PyObject_GetAttr etc.) and Objects/typeobject.c (the giant ~13kloc behemoth that implements type itself).

MRO and method lookup

Type objects have:

  • tp_bases — the explicit base list.
  • tp_mro — the C3-linearized method resolution order (a tuple, computed by type.__mro__).
  • tp_subclasses — a weak set of subclasses (used to invalidate caches when a base changes).

Objects/typeobject.c implements C3 in mro_internal and the cache invalidation in type_modified. Attribute lookup goes through _PyObject_GenericGetAttrWithDict, which walks the MRO honoring data descriptors then instance dict then non-data descriptors.

Built-in types

Type Source
object Objects/object.c (PyBaseObject_Type)
type Objects/typeobject.c
int Objects/longobject.c (formerly intobject.c + longobject.c)
float Objects/floatobject.c
complex Objects/complexobject.c
bool Objects/boolobject.c
bytes Objects/bytesobject.c
bytearray Objects/bytearrayobject.c
str (PEP 393) Objects/unicodeobject.c
tuple Objects/tupleobject.c
list Objects/listobject.c (Timsort lives here, see listsort.txt)
dict Objects/dictobject.c (compact dicts, PEP 468 ordering)
set / frozenset Objects/setobject.c
range Objects/rangeobject.c
slice Objects/sliceobject.c
function Objects/funcobject.c
method Objects/classobject.c
code Objects/codeobject.c
frame Objects/frameobject.c
generator / coroutine / async-gen Objects/genobject.c
module Objects/moduleobject.c
weakref Objects/weakrefobject.c
property, staticmethod, classmethod, descriptors Objects/descrobject.c
memoryview Objects/memoryobject.c
BaseException and friends Objects/exceptions.c
iter, reversed, enumerate, zip, map, filter Objects/iterobject.c, Objects/enumobject.c
TypeVar/ParamSpec/TypeVarTuple (PEP 695) Objects/typevarobject.c
OrderedDict Objects/odictobject.c
t-string template Objects/templateobject.c and Objects/interpolationobject.c (PEP 750, 3.14+)

The Objects/stringlib/ directory contains templated C used by bytes, bytearray, and str to share code via header-only includes (search-replace, find/index, partition, etc.).

Strings (PEP 393)

str is implemented as a flexible string representation: each instance picks the smallest of LATIN-1 / UCS-2 / UCS-4 storage based on its highest code point. The header in Include/cpython/unicodeobject.h describes the variants. A "compact" string puts the data immediately after the header; a "legacy" string allocates the data buffer separately.

String interning lives in Objects/unicodeobject.c and is documented in InternalDocs/string_interning.md. All identifiers used by the runtime (attribute names, builtin names, …) are interned at startup.

Dicts (compact + ordered)

CPython dicts are compact since 3.6: they store entries in insertion order in a dense array, with a separate sparse hash array of indices. This preserves order (PEP 468) without the memory overhead of an explicit linked list. The detailed layout is in Objects/dictobject.c with notes in Objects/dictnotes.txt.

Lists (Timsort)

list.sort and sorted use Timsort, a hybrid stable sort designed for real-world data. The algorithm and proof are in Objects/listsort.txt. Worst-case is O(n log n); sorted/nearly-sorted input is O(n).

Common pitfalls

  • Mutating tp_methods after type creation: not supported. Use PyType_Ready to initialise the methoddef-derived descriptors at startup.
  • Setting tp_basicsize smaller than the parent type: forbidden for heap types; CPython will refuse.
  • Storing PyObject* fields without participating in GC: leads to leaks if cycles are possible.
  • Comparing types with ==: works, but Py_IS_TYPE(obj, &Foo_Type) and PyObject_TypeCheck(obj, &Foo_Type) are the idiomatic forms.

Entry points for modification

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Object model – CPython wiki | Factory