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).

2. ob_refcnt (Reference Count)

3. ob_type (Type Pointer)


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:

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:

  1. Any specific object structure must declare PyObject (or PyVarObject) as its very first member.
  2. 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.