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 anob_tidfield tagging the owning thread. SeeInclude/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 types —
static PyTypeObject Foo_Type = { ... };declared in C. Cheap, but shared across all subinterpreters and not garbage-collected. - Heap types — created at runtime via
PyType_FromModuleAndSpecfrom aPyType_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 bytype.__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
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_methodsafter type creation: not supported. UsePyType_Readyto initialise the methoddef-derived descriptors at startup. - Setting
tp_basicsizesmaller 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, butPy_IS_TYPE(obj, &Foo_Type)andPyObject_TypeCheck(obj, &Foo_Type)are the idiomatic forms.
Entry points for modification
- New built-in type — add
Objects/<name>object.c, register fromPython/pylifecycle.c. - New protocol slot — add to
tp_as_*inInclude/cpython/object.h, updateObjects/typeobject.c. - Stdlib type implemented in C — generally goes in
Modules/_<name>module.cand usesPyType_FromModuleAndSpec. - Reference-count primitives —
Include/refcount.h.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.