Uniform Object Architecture: PyObject & PyVarObject

Explore the internal C layout of Python objects: PyObject headers, reference counting fields, PyVarObject containers, and the PyTypeObject slot vector dispatch system.

01. CPython Architecture, Object Model & Type System Mechanics7 min read

In CPython, everything is an object. Functions, modules, classes, integers, strings, and stack frames are all represented internally as heap-allocated C structures adhering to a uniform interface.

This structural uniformity allows the virtual machine to pass, inspect, and evaluate arbitrary data types using generic pointers (PyObject*), providing universal dynamic typing and seamless introspection.

Minimum PyObject HeaderC Layout
16 bytes

8 bytes for ob_refcnt + 8 bytes for ob_type pointer on standard 64-bit platforms.

1. The Anatomy of PyObject & PyVarObject#

Every CPython object begins with a mandatory header structure:

Architecture Diagram
Rendering diagram...

Mathematical Memory Footprint#

Let O\mathcal{O} be the set of all heap-allocated objects. The memory footprint M(o)M(o) for fixed-size versus variable-length objects is formalized as:

Mfixed(o)=sizeof(PyObject)+∑i=1ksizeof(fi)M_{\text{fixed}}(o) = \text{sizeof}(\text{PyObject}) + \sum_{i=1}^{k} \text{sizeof}(f_i)
Mvar(o)=sizeof(PyVarObject)+s(o)⋅sizeof(e)+∑j=1msizeof(fj)M_{\text{var}}(o) = \text{sizeof}(\text{PyVarObject}) + s(o) \cdot \text{sizeof}(e) + \sum_{j=1}^{m} \text{sizeof}(f_j)

Where:

  • sizeof(PyObject)=16\text{sizeof}(\text{PyObject}) = 16 bytes on 64-bit systems (88 bytes for ob_refcnt + 88 bytes for ob_type).
  • sizeof(PyVarObject)=24\text{sizeof}(\text{PyVarObject}) = 24 bytes on 64-bit systems (adding 88 bytes for ob_size).
  • s(o)s(o) represents the container item count.
  • ee represents stored item pointers (8 bytes per PyObject*).
  • fi,fjf_i, f_j represent type-specific internal bookkeeping fields.

2. Inspecting Object Headers in C and Python#

Compare how the C definition translates to Python memory addresses using the standard library ctypes module:

1
2
3
4
5
6
7
8
9
10
11
// CPython definition from Include/object.h typedef struct _object { _PyObject_HEAD_EXTRA // Double-linked list pointers in debug builds Py_ssize_t ob_refcnt; struct _typeobject *ob_type; } PyObject; typedef struct { PyObject ob_base; Py_ssize_t ob_size; // Number of items in variable-length objects } PyVarObject;

3. Dynamic Dispatch & The Type Slot System#

When Python executes an expression like a + b or len(container), how does the interpreter resolve which C function to run?

CPython does not perform slow string-based method dictionary lookups for fundamental operations. Instead, it inspects function pointers stored in dedicated memory slots within the object's PyTypeObject:

Architecture Diagram
Rendering diagram...

Major Type Slot Tables#

The PyTypeObject struct contains specific sub-tables grouping related operations:

Slot TableCategoryKey OperationsExample Functions
tp_as_numberArithmetic+, -, *, /, //, %, **nb_add, nb_multiply, nb_power
tp_as_sequenceLinear Containerslen(), x in s, s[i], concatenationsq_length, sq_item, sq_concat
tp_as_mappingKey-Value Lookupsm[key], len(m), assignmentmp_subscript, mp_ass_subscript
tp_as_asyncAsynchronous Protocolawait, async for, async witham_await, am_aiter, am_anext
Dual Nature Types
Some standard types implement multiple slot tables simultaneously. For example, list implements both tp_as_sequence (enabling index slicing and concatenation with +) and tp_as_mapping (enabling subscript reading and assignment via mp_subscript).

4. The Object Lifecycle: Allocation to Deallocation#

1

Step 1: Memory Allocation via `tp_alloc`

When an object is requested, CPython invokes the type's tp_alloc function. For standard Python objects, this requests a block of memory from the specialized small-object allocator (PyMalloc).

2

Step 2: Header Initialization

The newly allocated memory block has its ob_refcnt set to 1, and its ob_type pointer bound to the target class PyTypeObject. If it is a PyVarObject, ob_size is initialized to the requested element count.

3

Step 3: Instance Initialization via `tp_init`

The instance __init__ logic executes, populating user-defined instance attributes.

4

Step 4: Finalization & Deallocation via `tp_dealloc`

When ob_refcnt drops to zero, CPython immediately calls the type's tp_dealloc function. This releases nested references, cleans up external resources, and returns the memory block to the PyMalloc pool.

5. Concept Verification#

Test your understanding of CPython uniform object architecture:

Concept check

What are the two foundational fields present in the header of every PyObject in CPython?

Concept check

How does CPython execute operations like addition (a + b) with high performance?