PyObject Header Fields in CPython Explained
In CPython, every Python object is represented under the hood by a C
structure whose foundation is PyObject. This article
examines the internal layout of the PyObject structure
header, breaking down the specific fields that facilitate CPython's
reference counting, dynamic typing, and memory tracking mechanisms.
The Core PyObject
Definition
In the CPython source code (specifically within
Include/object.h), the base structure is defined as:
struct _object {
_PyObject_HEAD_EXTRA
union {
Py_ssize_t ob_refcnt;
#if SIZEOF_VOID_P > 4
uint32_t ob_refcnt_split[2];
#endif
};
PyTypeObject *ob_type;
};This structure is standardized using the macro
PyObject_HEAD. When fully expanded in a standard release
build, PyObject consists of two primary fields, with
additional fields enabled conditionally in specialized builds.
1. ob_refcnt
(Reference Count)
- Type:
Py_ssize_t(signed integer matching pointer width) - Purpose: Memory management and garbage collection
CPython uses reference counting as its primary memory management
strategy. The ob_refcnt field records how many active
references currently point to the object.
- When a variable is assigned, passed to a function, or stored in a
collection,
ob_refcntincrements. - When a reference falls out of scope or is explicitly deleted,
ob_refcntdecrements. - When
ob_refcntdrops to zero, the object's deallocation routine is invoked immediately to release its memory.
In Python 3.12 and newer, ob_refcnt supports "immortal
objects" (such as None, True,
False, and small integers), where specific high-bit flags
prevent standard increments and decrements, eliminating cache-line
bouncing across multi-core systems.
2. ob_type (Type
Pointer)
- Type:
PyTypeObject*(pointer to astruct _typeobject) - Purpose: Dynamic typing and method dispatch
Because Python is dynamically typed, the object itself must store
information about what type of data it represents. The
ob_type pointer links the object instance to its
corresponding type object (such as &PyLong_Type,
&PyList_Type, or user-defined classes).
The type object contains:
- Function pointers implementing object behavior (e.g.,
tp_alloc,tp_dealloc,tp_repr). - Pointers to protocol suites (numeric, sequence, mapping).
- Memory sizing information.
- The method resolution order (MRO) and class attribute dictionaries.
When an operation like len(obj) or
obj + other is executed, CPython inspects
obj->ob_type to resolve the corresponding C
function.
3.
_PyObject_HEAD_EXTRA (Debug Fields)
- Fields:
struct _object *_ob_next; struct _object *_ob_prev; - Condition: Active only when
Py_TRACE_REFSis defined (debug builds)
In a standard release build, _PyObject_HEAD_EXTRA
evaluates to empty code and consumes zero bytes. In debug configurations
with Py_TRACE_REFS enabled, it inserts two pointer
fields—_ob_next and _ob_prev.
These pointers place every allocated Python object into a global, doubly linked list of all active heap objects. This enables runtime heap inspection, leak detection, and debugging tools within CPython.
Extension:
PyVarObject and ob_size
Variable-length objects (such as str,
tuple, list, and bytes) extend
PyObject using the PyVarObject structure,
which includes PyObject_VAR_HEAD:
struct _varobject {
PyObject ob_base;
Py_ssize_t ob_size; /* Number of items in variable part */
};ob_size(Py_ssize_t): Tracks the number of elements contained in the object (e.g., the length of atupleor number of items in alist). Fixed-size types (such asfloat) do not possess this field.