Skip to main content

Objects/object.c

cpython 3.14 @ ab2d84fe1023/Objects/object.c

Foundation of the object model. Provides PyObject_Init, PyObject_Repr/Str/ASCII, PyObject_Hash, PyObject_RichCompare, PyObject_GetAttr/SetAttr, PyObject_IsTrue, and the reference-count functions. Also contains the free-threaded reference count merge path (_Py_MergeZeroLocalRefcount) and the debug refchain infrastructure (_Py_AddToAllObjects).

Map

LinesSymbolRolegopy
48-270_PyObject_CheckConsistency, reftotal_add, _Py_FinalizeRefTotal, _PyRefchain_*, _Py_NegativeRefcount, Py_IncRef/DecRef, _Py_IncRef/_Py_DecRefRefcount debug helpers and the primary inc/dec entry points.objects/object.go
271-530_Py_DecRefSharedIsDead, _Py_DecRefShared, _Py_MergeZeroLocalRefcount, _Py_ExplicitMergeRefcount, _PyObject_ResurrectEndSlowFree-threaded refcount paths (compiled only under Py_GIL_DISABLED).not ported (Go GC)
530-575PyObject_Init, PyObject_InitVar, _PyObject_New, _PyObject_NewVarAllocators and type-member initialization.objects/object.go:Init
576-630PyObject_CallFinalizer, PyObject_CallFinalizerFromDealloctp_finalize dispatch; handles the resurrection guard.objects/object.go:CallFinalizer
631-756PyObject_Print, _PyObject_DumpDebug printers; write repr to a FILE or stderr.objects/object.go:Dump
757-900PyObject_Repr, PyObject_Str, PyObject_ASCIIString conversion dispatch via tp_repr/tp_str.objects/object.go:Repr, Str, ASCII
900-1100PyObject_GetAttr, PyObject_GenericGetAttr, PyObject_SetAttr, PyObject_GenericSetAttrAttribute access and the generic MRO-based lookup.objects/object.go:GetAttr, SetAttr
1100-1350PyObject_RichCompare, PyObject_RichCompareBool, do_richcompareRich comparison dispatch (==, <, >, etc.).objects/object.go:RichCompare
1350-1600PyObject_Hash, PyObject_HashNotImplemented, _Py_HashPointer, _Py_HashDouble, _Py_HashBytesHash dispatch and the low-level hash primitives.objects/object.go:Hash
1600-1900PyObject_IsTrue, PyObject_Not, PyObject_Length, PyObject_LengthHintTruth testing and length/size protocol.objects/object.go:IsTrue, Length
1900-2200PyObject_Dir, _PyObject_GetMethod, _PyObject_NextNotImplementeddir() implementation and method lookup fast path.objects/object.go:Dir, GetMethod
2200-2600_PyObject_GetItemId, PyObject_GetItem, PyObject_SetItem, PyObject_DelItemItem access via tp_as_mapping and tp_as_sequence.objects/object.go:GetItem, SetItem
2600-3395PyObject_Format, _PyObject_IsAbstract, Py_ReprEnter/Leave, _Py_Dealloc, type slot machineryFormatting, cycle guard, and final deallocation path.objects/object.go:Format, Dealloc

Reading

Reference count basics (lines 335 to 360)

cpython 3.14 @ ab2d84fe1023/Objects/object.c#L335-360

void
Py_IncRef(PyObject *o)
{
if (_Py_IsImmortal(o)) {
return;
}
_Py_INCREF_STAT_INC();
#ifdef Py_REF_DEBUG
reftotal_add(tstate, 1);
#endif
o->ob_refcnt++;
}

void
Py_DecRef(PyObject *o)
{
if (_Py_IsImmortal(o)) {
return;
}
_Py_DECREF_STAT_INC();
#ifdef Py_REF_DEBUG
reftotal_add(tstate, -1);
#endif
if (--o->ob_refcnt == 0) {
_Py_Dealloc(o);
}
#ifdef Py_GIL_DISABLED
else if (o->ob_refcnt < 0) {
_Py_NegativeRefcount(filename, lineno, o);
}
#endif
}

Py_DecRef calls _Py_Dealloc when the count hits zero. In free-threaded builds Py_DecRef delegates to _Py_DecRefShared for objects shared across threads. The immortal-object fast path (_Py_IsImmortal) skips the refcount update entirely for singletons like None, True, False, and small integers.

Free-threaded merge path (lines 435 to 493)

cpython 3.14 @ ab2d84fe1023/Objects/object.c#L435-493

void
_Py_MergeZeroLocalRefcount(PyObject *op)
{
// Only called when Py_GIL_DISABLED is defined.
// Each thread has a local refcount "bias"; when a thread drops
// its bias to zero, the object may reach zero globally.
Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&op->ob_ref_shared);
if (shared == 0) {
_Py_Dealloc(op);
return;
}
...
// Merge local count into shared and check for true zero.
}

Each thread maintains a local refcount bias to avoid atomic operations on every inc/dec. When a thread drops its local bias, it calls this function to merge into the shared count and deallocate if the combined count is truly zero. This function is compiled only when Py_GIL_DISABLED is defined (PEP 703, free-threaded CPython 3.13+). gopy does not port this path; Go's garbage collector handles object lifetime instead.

PyObject_Repr (lines 757 to 798)

cpython 3.14 @ ab2d84fe1023/Objects/object.c#L757-798

PyObject *
PyObject_Repr(PyObject *v)
{
PyObject *res;
if (PyErr_CheckSignals())
return NULL;
if (Py_ReprEnter(v) != 0)
return NULL;
res = (*Py_TYPE(v)->tp_repr)(v);
Py_ReprLeave(v);
if (res == NULL)
return NULL;
if (!PyUnicode_Check(res)) {
PyErr_Format(PyExc_TypeError,
"__repr__ returned non-string (type %.200s)",
Py_TYPE(res)->tp_name);
Py_DECREF(res);
return NULL;
}
return res;
}

Calls tp_repr, checks the return is a str (raises TypeError if not), and enforces the Py_ReprEnter/Py_ReprLeave cycle guard. The guard is a per-thread set; if v is already in it, Py_ReprEnter returns non-zero and the call returns NULL without entering recursion, causing the caller to display '...' for the cycle. Py_ReprLeave removes the object from the set on exit.

do_richcompare (lines 1100 to 1200)

cpython 3.14 @ ab2d84fe1023/Objects/object.c#L1100-1200

static PyObject *
do_richcompare(PyObject *v, PyObject *w, int op)
{
richcmpfunc f;
PyObject *res;

if (!Py_IS_TYPE(v, Py_TYPE(w)) &&
PyType_IsSubtype(Py_TYPE(w), Py_TYPE(v)) &&
(f = Py_TYPE(w)->tp_richcompare) != NULL) {
res = (*f)(w, v, _Py_SwappedOp[op]);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
if ((f = Py_TYPE(v)->tp_richcompare) != NULL) {
res = (*f)(v, w, op);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
if ((f = Py_TYPE(w)->tp_richcompare) != NULL) {
res = (*f)(w, v, _Py_SwappedOp[op]);
if (res != Py_NotImplemented)
return res;
Py_DECREF(res);
}
/* If neither object implements it, provide a sensible default
for == and !=, but raise an exception for ordering. */
switch (op) {
case Py_EQ:
res = (v == w) ? Py_True : Py_False;
break;
case Py_NE:
res = (v != w) ? Py_True : Py_False;
break;
default:
PyErr_Format(PyExc_TypeError, ...);
return NULL;
}
return Py_NewRef(res);
}

The engine behind ==, <, >, etc. Tries tp_richcompare on the left operand, then the right (with the reflected operator via _Py_SwappedOp). Subtype operands get priority on the right-hand side to allow subclasses to override parent comparisons. Falls back to identity comparison for ==/!=. Returns NotImplemented (not None) as the sentinel for unimplemented comparisons; callers convert that to TypeError.

PyObject_Hash (lines 1350 to 1450)

cpython 3.14 @ ab2d84fe1023/Objects/object.c#L1350-1450

Py_hash_t
PyObject_Hash(PyObject *v)
{
Py_uhash_t x;
hashfunc tp_hash = Py_TYPE(v)->tp_hash;
if (tp_hash != NULL)
return (*tp_hash)(v);
/* If there's no tp_hash but there is tp_richcompare, the object
defines __eq__ without __hash__ and is therefore unhashable. */
if (Py_TYPE(v)->tp_richcompare != NULL) {
PyErr_Format(PyExc_TypeError, "unhashable type: '%.200s'",
Py_TYPE(v)->tp_name);
return -1;
}
return _Py_HashPointer(v);
}

Calls tp_hash. If NULL, checks for tp_richcompare presence: an object that defines __eq__ but not __hash__ is unhashable by the data model, and the check enforces that. Falls back to _Py_HashPointer for objects with neither. The pointer hash function uses a Fibonacci-hashing multiply to spread the low bits so that sequential heap addresses do not cluster in hash tables.

_Py_Dealloc (lines 3300 to 3395)

cpython 3.14 @ ab2d84fe1023/Objects/object.c#L3300-3395

void
_Py_Dealloc(PyObject *op)
{
PyTypeObject *type = Py_TYPE(op);
destructor dealloc = type->tp_dealloc;
#ifdef Py_DEBUG
_PyObject_CheckConsistency(op, 0);
#endif
if (type->tp_finalize != NULL) {
if (PyObject_CallFinalizerFromDealloc(op) < 0) {
/* Resurrected */
return;
}
}
if (type->tp_free != NULL)
(*type->tp_free)(op);
else
dealloc(op);
...
}

Calls tp_finalize first if set, and checks whether the object was resurrected inside the finalizer (the resurrection guard). Then calls tp_dealloc. In debug builds, _PyObject_CheckConsistency runs before dealloc to catch inconsistent ob_type or ob_refcnt states. The type name is tracked for sys.getobjects().

In gopy, _Py_Dealloc maps to the Go garbage collector finalizer registered via runtime.SetFinalizer. The resurrection guard is not reproduced because Go's GC does not permit object resurrection.

gopy mirror

objects/object.go. The dispatch functions (Repr, Str, Hash, RichCompare, GetAttr) are methods on *Object. The free-threaded refcount merge path is not ported; gopy uses Go's GC instead. _Py_Dealloc becomes the Go garbage collector finalizer registered via runtime.SetFinalizer.

CPython 3.14 changes

Immortal objects (PEP 683) landed in 3.12. Free-threaded refcount biasing (_Py_DecRefShared, _Py_MergeZeroLocalRefcount) is new in 3.13 (PEP 703). _PyObject_ResurrectEndSlow is a 3.14 addition to handle the resurrection race in the free-threaded GC.