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— needstp_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 ofPyThreadStates.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_stringis executing.
_PyRuntimeState
The single global. In Include/internal/pycore_runtime.h:
interpreters.head— head of the linked list ofPyInterpreterState.gilstate.tstate_current— atomic pointer to the current thread'sPyThreadState.signals— pending-signal flags.audit_hooks—sys.auditlisteners.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.