This document describes CPython's core object model and type system. It covers the fundamental structures (PyObject, PyVarObject, PyTypeObject) that underlie all Python objects, the reference counting memory management system, type metadata and method resolution, and optimization mechanisms like type versioning and caching.
For information about dictionary implementation details, see Dictionary and Container Objects. For garbage collection of cyclic references, see Reference Counting and Garbage Collection.
Every Python object in CPython is represented by a PyObject structure or an extension of it. This structure contains the minimal fields required for all objects: reference count information and a pointer to the object's type.
Default Build (with GIL):
PyObject {
Py_ssize_t ob_refcnt; // Reference count
PyTypeObject *ob_type; // Pointer to type object
}
Free-Threaded Build (Py_GIL_DISABLED):
PyObject {
uint32_t ob_ref_local; // Thread-local reference count
uint32_t ob_ref_shared; // Shared reference count
uintptr_t ob_tid; // Owning thread ID
uint16_t _padding;
PyTypeObject *ob_type; // Pointer to type object
}
In the free-threaded build, reference counting is split between thread-local and shared counts to reduce contention. The ob_tid field identifies the thread that "owns" the object for fast-path operations.
Sources: Include/cpython/object.h148-242 Include/internal/pycore_object.h74-102
Objects are initialized using _PyObject_Init() which sets the type pointer and initializes the reference count to 1. The type's reference count is also incremented.
Sources: Include/internal/pycore_object.h484-492
Variable-sized objects (lists, tuples, strings, etc.) extend PyObject with an additional ob_size field that stores the number of items:
PyVarObject {
PyObject ob_base; // Base PyObject fields
Py_ssize_t ob_size; // Number of items
}
The actual item storage follows immediately after the structure in memory. The ob_size field is distinct from the tp_itemsize in the type, which specifies the size of each individual item.
Sources: Include/cpython/object.h148-242
PyTypeObject is the structure that defines a Python type (class). It contains all metadata about the type, including its name, size, methods, and various function pointers (slots).
Key Fields:
"builtins.list"Py_TPFLAGS_*)__dict__)Sources: Include/cpython/object.h148-242 Objects/typeobject.c1-100
The tp_flags field uses various Py_TPFLAGS_* constants to indicate type characteristics:
| Flag | Purpose |
|---|---|
Py_TPFLAGS_HEAPTYPE | Type allocated on heap (not static) |
Py_TPFLAGS_READY | Type has been initialized by PyType_Ready() |
Py_TPFLAGS_HAVE_GC | Type supports garbage collection |
Py_TPFLAGS_BASETYPE | Can be subclassed |
_Py_TPFLAGS_STATIC_BUILTIN | Static builtin type with per-interpreter state |
Py_TPFLAGS_MANAGED_DICT | Instances use managed dict |
Py_TPFLAGS_MANAGED_WEAKREF | Instances use managed weakrefs |
Sources: Objects/typeobject.c458-483
Heap types (created at runtime via type() or class statements) use PyHeapTypeObject, which extends PyTypeObject:
PyHeapTypeObject {
PyTypeObject ht_type; // Base type structure
PyAsyncMethods as_async; // Async method suite
PyNumberMethods as_number; // Number methods
PyMappingMethods as_mapping; // Mapping methods
PySequenceMethods as_sequence; // Sequence methods
PyBufferProcs as_buffer; // Buffer protocol
PyObject *ht_name; // Name object
PyObject *ht_slots; // __slots__ tuple
PyObject *ht_qualname; // Qualified name
PyObject *ht_module; // Defining module
PyDictKeysObject *ht_cached_keys; // Shared keys for instance dicts
_specialization_cache _spec_cache; // Specialization cache
Py_ssize_t unique_id; // Unique ID (free-threaded)
}
The specialization cache stores optimized versions of methods like __getitem__ for performance.
Sources: Include/cpython/object.h265-318
Every type is an instance of type (or a metaclass derived from it). Most types inherit from object as their base class. The type object itself is its own type (metaclass) and inherits from object.
Sources: Objects/typeobject.c148-242
The MRO determines the order in which base classes are searched when looking up attributes. It's stored in tp_mro as a tuple and computed using the C3 linearization algorithm.
The lookup process:
type(obj)->tp_mro[0] (the type itself)tp_dictAttributeErrorSources: Objects/typeobject.c647-711 Include/internal/pycore_typeobject.h98-100
Reference counting is CPython's primary memory management mechanism. Every object tracks how many references point to it, and is deallocated when the count reaches zero.
Immortal objects have a very high reference count (stored as a special value) and are never deallocated. Examples include None, True, False, and frequently used small integers.
Sources: Include/internal/pycore_object.h134-179 Objects/object.c334-356
In the free-threaded build, reference counting is more complex to avoid contention:
The free-threaded build uses:
When an object's local refcount reaches zero, the counts must be merged to determine if deallocation is needed.
Sources: Include/internal/pycore_object.h358-432 Objects/object.c358-524
Types themselves use reference counting, but with special handling:
_Py_INCREF_TYPE() and _Py_DECREF_TYPE() macros provide optimized handlingIn free-threaded builds, frequently-used type references use per-thread refcount tables to reduce contention.
Sources: Include/internal/pycore_object.h314-409
CPython uses versioning to validate cached lookups and enable optimizations.
Each type has a tp_version_tag that's incremented whenever the type is modified. This allows cached lookups to check if they're still valid.
When a type is modified (e.g., adding/removing methods), its version tag is set to 0, invalidating all caches. On next lookup, a new version tag is assigned.
Sources: Objects/typeobject.c1149-1197 Objects/typeobject.c987-1047
The global method cache (type_cache) uses (tp_version_tag, name_hash) as a key:
The cache hash is computed as:
In free-threaded builds, cache entries use sequence locks for lock-free reads.
Sources: Objects/typeobject.c40-57 Objects/typeobject.c987-1047
PyHeapTypeObject includes a _spec_cache structure for specialized optimizations:
This enables fast-path execution for common operations like obj[key] when the types haven't changed.
Sources: Include/cpython/object.h249-263
Type watchers allow external code to be notified of type modifications:
Watchers are used by:
Each type has a tp_watched bitfield indicating which watchers are interested in it.
Sources: Objects/typeobject.c1069-1146
Static types are defined at compile time and exist for the lifetime of the interpreter. They have _Py_TPFLAGS_STATIC_BUILTIN set.
Per-interpreter state for static types is stored in managed_static_type_state:
This allows multiple interpreters to have independent state for shared static types.
Sources: Objects/typeobject.c231-310 Objects/typeobject.c453-456
Heap types are created at runtime (via type() or class statements). They:
Py_TPFLAGS_HEAPTYPE setPyHeapTypeObject structureThe ht_module field points to the defining module, enabling per-module state access.
Sources: Include/cpython/object.h265-318
Both static and heap types must be initialized via PyType_Ready():
This fills in inherited slots, builds the MRO, populates the type's dictionary, and prepares the type for use.
Sources: Objects/typeobject.c881-912 Objects/typeobject.c484-525
Types define behavior through slot functions (function pointers in PyTypeObject). Key slots include:
| Slot | Purpose |
|---|---|
tp_dealloc | Deallocate object |
tp_hash | Compute hash value |
tp_call | Make object callable |
tp_str | Convert to string |
tp_getattro | Get attribute |
tp_setattro | Set/delete attribute |
tp_descr_get | Descriptor __get__ |
tp_descr_set | Descriptor __set__ |
tp_init | Initialize instance |
tp_new | Create new instance |
The descriptor protocol (tp_descr_get/tp_descr_set) enables properties, methods, and other attribute access customization.
Sources: Include/cpython/object.h148-242 Objects/typeobject.c7000-8000
In the free-threaded build (Py_GIL_DISABLED), type modifications require synchronization:
The global type lock protects:
tp_version_tag updatestp_mro, tp_bases, tp_base changes_spec_cache modificationsFor major modifications (changing slots/flags), CPython uses "stop-the-world":
This ensures readers don't see inconsistent state when types are being restructured.
Sources: Objects/typeobject.c61-196
New heap types start in an "unrevealed" state before being exposed to other threads. The _Py_TYPE_REVEALED_FLAG indicates a type has been shared:
Unrevealed types can be modified without locks, improving initialization performance.
Sources: Objects/typeobject.c95-119
Refresh this wiki