PyObject Internal Layout in CPython
In CPython, the standard C implementation of Python, every entity is
an object represented in memory by a C structure. At the foundation of
this object model lies PyObject, the base structure from
which all Python objects derive. This article breaks down the internal
memory layout of PyObject, details its core
fields—including reference counts and type pointers—explains how
variable-length objects extend it via PyVarObject, and
demonstrates how C-level inheritance functions in the Python
runtime.
The C Definition of
PyObject
In CPython source code (specifically Include/object.h),
PyObject is defined conceptually through a macro named
PyObject_HEAD. When expanded in a standard release build,
the structure is essentially:
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
} PyObject;On modern 64-bit systems, this base structure occupies exactly 16 bytes of memory (excluding debug overhead).
Field Breakdown
1.
_PyObject_HEAD_EXTRA
This macro is used exclusively for tracking active objects when
Python is compiled with the Py_TRACE_REFS build
configuration (used primarily for debugging memory leaks).
- Release builds: Expands to nothing and consumes 0 bytes.
- Debug builds: Expands to two pointers
(
struct _object *_ob_next; struct _object *_ob_prev;) used to maintain a doubly linked list of all live objects on the heap.
2. ob_refcnt
(Reference Count)
- Type:
Py_ssize_t(signed 64-bit integer on 64-bit systems, signed 32-bit integer on 32-bit systems). - Role: Tracks how many references point to this
object across the runtime. When
ob_refcntreaches zero, the memory deallocator associated with the object is triggered. - Modern CPython Note: In Python 3.12 and later,
CPython introduced "immortal objects" (PEP 683). Immortal objects (like
None,True,False, and small integers) use specific bit representations inob_refcntso that reference count modification operations can be skipped, improving cache efficiency and multi-core scalability.
3. ob_type (Type
Pointer)
- Type:
struct _typeobject *(8-byte pointer on 64-bit systems). - Role: Points to the type descriptor object (an
instance of
PyTypeObject) representing the object's Python type (such asint,str, or a user-defined class). - The type object contains function pointers for handling operations such as allocation, deallocation, arithmetic operations, and string representation.
The Memory Layout
On a standard 64-bit architecture without debug flags, the linear
memory layout of a PyObject is aligned as follows:
Offset (bytes) Field Size (bytes) Description
+---------------+------------+--------------+----------------------------------+
| 0x00 - 0x07 | ob_refcnt | 8 | Reference count |
+---------------+------------+--------------+----------------------------------+
| 0x08 - 0x0F | ob_type | 8 | Pointer to the type object |
+---------------+------------+--------------+----------------------------------+
Total Size: 16 bytes
Variable-Length Objects:
PyVarObject
Objects with variable lengths—such as tuples, lists, strings, and
dictionaries—cannot store all necessary metadata in the fixed 16-byte
PyObject structure. They use an extended base structure
called PyVarObject.
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;PyVarObject prepends the PyObject layout
and appends an ob_size field:
ob_size(8 bytes on 64-bit): Holds the number of elements currently stored in the object. For atuple, this represents the tuple length. For aPyLongObject(Python integer), it represents the number of internal digits and holds the sign of the integer.
Offset (bytes) Field Size (bytes) Description
+---------------+------------+--------------+----------------------------------+
| 0x00 - 0x07 | ob_refcnt | 8 | Reference count (from PyObject) |
+---------------+------------+--------------+----------------------------------+
| 0x08 - 0x0F | ob_type | 8 | Type pointer (from PyObject) |
+---------------+------------+--------------+----------------------------------+
| 0x10 - 0x17 | ob_size | 8 | Item count |
+---------------+------------+--------------+----------------------------------+
Total Base Size: 24 bytes
Structural Subtyping in C
C does not support object-oriented inheritance natively. CPython achieves polymorphism and subtyping by enforcing strict memory alignment rules:
- Any specific object structure must declare
PyObject(orPyVarObject) as its very first member. - The C standard guarantees that a pointer to a struct can be safely cast to a pointer to its first member without an offset change.
For example, a Python float is defined as:
typedef struct {
PyObject ob_base;
double ob_fval;
} PyFloatObject;Because ob_base is the first member, the memory address
of PyFloatObject is identical to the memory address of its
internal PyObject:
Pointer Address ---> [ ob_refcnt (8B) ]
[ ob_type (8B) ]
[ ob_fval (8B) ]
This layout allows the CPython runtime to cast any pointer of type
PyFloatObject*, PyListObject*, or custom
extension type directly to PyObject* to access metadata,
manipulate reference counts, and inspect types uniformly.