Open-Source Wikis

/

CPython

/

Reference

/

Data models

python/cpython

Data models

This page collects the layout of the most-touched runtime structures in CPython. Field names and offsets follow the headers as of main (3.15 alpha); the sizes change between releases.

PyObject

struct _object {
    Py_ssize_t   ob_refcnt;
    PyTypeObject *ob_type;
};

Variable-sized (PyVarObject) adds an ob_size field:

typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size;
} PyVarObject;

Free-threaded builds replace ob_refcnt with a split refcount (an owning-thread counter and a shared atomic counter) plus an ob_tid field; the layout is in Include/refcount.h.

GC-tracked objects have a PyGC_Head immediately before the PyObject. It holds the doubly-linked list pointers used during a collection plus the generation tag. Layout in Include/internal/pycore_gc.h.

PyTypeObject

The full struct is huge — see Include/cpython/object.h. Major groups:

  • Identity: tp_name, tp_basicsize, tp_itemsize, tp_flags.
  • Allocation/lifecycle: tp_alloc, tp_new, tp_init, tp_dealloc, tp_finalize, tp_free.
  • Iteration: tp_iter, tp_iternext.
  • Comparison: tp_richcompare, tp_hash.
  • Calling: tp_call, vectorcall_offset.
  • Attribute access: tp_getattro, tp_setattro, tp_descr_get, tp_descr_set.
  • GC: tp_traverse, tp_clear.
  • Subprotocols: tp_as_number (nb_*), tp_as_sequence (sq_*), tp_as_mapping (mp_*), tp_as_buffer (bf_*), tp_as_async (am_*).
  • Inheritance: tp_bases, tp_mro, tp_subclasses, tp_dict, tp_dictoffset, tp_weaklistoffset.
  • Methods/members: tp_methods, tp_members, tp_getset.

Py_TPFLAGS_* bit flags include:

  • Py_TPFLAGS_HAVE_GC — needs tp_traverse/tp_clear.
  • Py_TPFLAGS_BASETYPE — can be subclassed.
  • Py_TPFLAGS_HEAPTYPE — created at runtime.
  • Py_TPFLAGS_IS_ABSTRACT — has abstract methods.
  • Py_TPFLAGS_LONG_SUBCLASS, Py_TPFLAGS_LIST_SUBCLASS, Py_TPFLAGS_TUPLE_SUBCLASS, Py_TPFLAGS_BYTES_SUBCLASS, Py_TPFLAGS_UNICODE_SUBCLASS, Py_TPFLAGS_DICT_SUBCLASS, Py_TPFLAGS_BASE_EXC_SUBCLASS, Py_TPFLAGS_TYPE_SUBCLASS — fast-path tags.

PyCodeObject

Defined in Include/cpython/code.h. Selected fields:

Field Meaning
co_code_adaptive Pointer to the bytecode (16-bit code units, in-place rewritable).
co_consts Tuple of constants referenced by the bytecode.
co_names Tuple of names (LOAD_NAME / LOAD_GLOBAL).
co_varnames, co_cellvars, co_freevars Local / cell / free variables.
co_argcount, co_posonlyargcount, co_kwonlyargcount Function signature shape.
co_nlocals, co_stacksize Frame sizing.
co_flags CO_* flags (CO_OPTIMIZED, CO_NEWLOCALS, CO_GENERATOR, …).
co_filename, co_qualname, co_firstlineno Identity.
co_linetable Compact line-table (PEP 626/657 column data).
co_exceptiontable Exception-table bytes (see Exception handling).
co_executors Array of tier-2 executors keyed off ENTER_EXECUTOR opargs.

A PyCodeObject is itself a Python value (code type) with tp_basicsize ~600 bytes, but it points at variably-sized arrays for the bytecode and tables.

_PyInterpreterFrame

Defined in Include/internal/pycore_frame.h. Lives in the interpreter's data stack, not on the C stack. Selected fields:

Field Meaning
f_executable The PyCodeObject (or callable wrapper) being executed.
f_funcobj The PyFunctionObject (or NULL for top-level frames).
f_globals / f_builtins / f_locals The three name lookups.
prev_instr Pointer just before the next instruction (for tracing).
previous Caller frame.
localsplus[] Locals, then cells, then the value stack.
frame_obj A lazily-materialized PyFrameObject (the public frame type).
instr_ptr Where execution resumes after a yield/await/dispatch.
owner One of FRAME_OWNED_BY_THREAD, FRAME_OWNED_BY_GENERATOR, FRAME_OWNED_BY_FRAME_OBJECT.

The frame redesign was a 3.11 change; details in InternalDocs/frames.md.

PyThreadState (selected fields)

struct _ts {
    PyThreadState *prev, *next;
    PyInterpreterState *interp;

    _PyInterpreterFrame *cframe;       // current frame (data-stack)
    int recursion_remaining;
    int tracing;
    int tracing_what;

    PyObject *current_exception;       // active exception
    PyObject *exc_state;               // exception stack (PEP 654)

    PyObject *dict;                    // thread-local for GIL-held threads

    _PyEvalBreakerEntry *eval_breaker; // signal the dispatch loop to break

    PyObject *async_gen_firstiter, *async_gen_finalizer;
    PyObject *context;                 // current contextvars Context

    Py_ssize_t id;                     // unique within process
    pthread_t native_thread_id;
};

The full layout is in Include/cpython/pystate.h and Include/internal/pycore_pystate.h. The cframe indirection is what lets the interpreter swap dispatch tables (e.g. for tracing or tier-2 entry).

PyInterpreterState (selected fields)

In Include/internal/pycore_interp.h:

  • id, next, parent.
  • gil — per-interpreter GIL state (when enabled).
  • threads — linked list of PyThreadStates.
  • modules, modules_by_index, builtins, sys, importlib — per-interpreter import state.
  • eval_frame — overridable per PEP 523.
  • obmalloc, gc — per-interpreter allocator and GC state.
  • executor_blooms, executor_ptrs — tier-2 executor invalidation arrays.
  • code_state, func_state, tuple_state, list_state, dict_state, frame_state, set_state — per-interpreter freelists and small-object caches.
  • int_state, float_state — small-int cache, float freelist.
  • xi_state — cross-interpreter data state (Python/crossinterp.c).
  • interpreters_main_thread — set once an _interpreters.run_string is executing.

_PyRuntimeState

The single global. In Include/internal/pycore_runtime.h:

  • interpreters.head — head of the linked list of PyInterpreterState.
  • gilstate.tstate_current — atomic pointer to the current thread's PyThreadState.
  • signals — pending-signal flags.
  • audit_hookssys.audit listeners.
  • unicode_state, dict_state, obmalloc.global — process-wide caches that can be shared across interpreters because they're immutable after init.
  • static_objects — the singletons (Py_None, Py_True, Py_False, Py_Ellipsis, …) lifted into runtime so they can be reused across interpreters.

PyModuleObject and per-module state

A heap-type-friendly module gets a per-instance m_state (size declared by PyModuleDef.m_size). To access it from a method, use PyModule_GetState(self). State for types defined inside the module goes through PyType_GetModuleState. This is how multi-phase init achieves subinterpreter isolation.

Inline cache structures

Per-opcode inline caches are embedded directly in the bytecode stream as additional _Py_CODEUNITs. The shape per opcode is in Include/internal/pycore_code.h (e.g. _PyBinaryOpCache, _PyLoadGlobalCache). The size in code units is captured in Lib/_opcode_metadata.py (generated from Python/bytecodes.c).

A representative example:

typedef struct {
    uint16_t counter;
    uint16_t version[2];     // 2 code units → 32 bits
    uint16_t index;
} _PyAttrCache;

The counter arms specialization; the version is the type's tp_version_tag snapshot the cache was built against.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Data models – CPython wiki | Factory