Skip to main content

Objects/abstract.c

cpython 3.14 @ ab2d84fe1023/Objects/abstract.c

Abstract object protocol layer. Every operation that Python bytecode performs on objects goes through this file (or the type's slot directly). PyObject_Call and vectorcallfunc are the entry points for all Python calls; PyNumber_Add, PySequence_GetItem, PyMapping_Keys etc. implement the protocol fallbacks and coercions. The file also contains PyIter_Next and the aiter/anext async iteration helpers.

Map

LinesSymbolRolegopy
1-80abstract_get_bases, abstract_issubclassWalk __bases__ tuples for issubclass with abstract classes.objects/protocol.go:abstractIssubclass
81-200object_recursive_isinstance, PyObject_IsInstance, PyObject_IsSubclassisinstance / issubclass with __instancecheck__ / __subclasshook__.objects/protocol.go:IsInstance, IsSubclass
201-350_PyObject_CallMethod, _PyObject_CallMethodIdObjArgs, _PyObject_CallMethodOneArgConvenience wrappers: look up a method by name and call it.objects/protocol.go:CallMethod
351-500PyObject_CallNoArgs, PyObject_CallOneArg, PyObject_CallFunctionThin wrappers that build arg tuples and forward to PyObject_Call.objects/protocol.go:CallNoArgs, CallOneArg
501-700PyObject_Call, _PyObject_Call, _PyObject_FastCallDictCore call dispatch: tp_call or vectorcallfunc.objects/protocol.go:Call
701-900PyObject_Vectorcall, PyObject_VectorcallMethod, _PyObject_VectorcallTstateVectorcall fast path: C array + kwnames, no tuple allocation.objects/protocol.go:Vectorcall
901-1000binary_op, binary_op1Engine for all binary numeric operations: try left, then right (reflected).objects/protocol.go:binaryOp
1001-1100ternary_opEngine for pow(base, exp, mod) (nb_power).objects/protocol.go:ternaryOp
1101-1300PyNumber_Add, PyNumber_Subtract, PyNumber_Multiply, PyNumber_TrueDivide, PyNumber_FloorDivide, PyNumber_Remainder, PyNumber_Lshift, PyNumber_Rshift, PyNumber_Or, PyNumber_And, PyNumber_XorBinary numeric ops, each delegating to binary_op.objects/protocol.go
1301-1400PyNumber_InPlaceAdd, PyNumber_InPlaceSubtract, etc.In-place numeric ops; try nb_inplace_* first, fall back to nb_*.objects/protocol.go
1401-1600PyNumber_Long, PyNumber_Float, PyNumber_Index, PyNumber_AsSsize_tNumeric coercions: __int__, __float__, __index__, size clamping.objects/protocol.go:NumberLong, NumberIndex
1601-1750PySequence_Length, PySequence_Concat, PySequence_Repeat, PySequence_GetItem, PySequence_SetItem, PySequence_DelItemCore sequence operations.objects/protocol.go:SequenceGetItem, SequenceLength
1751-1900PySequence_Count, PySequence_Contains, PySequence_IndexSequence search ops; Contains falls back to linear scan.objects/protocol.go:SequenceContains
1901-2050PySequence_List, PySequence_Tuple, PySequence_FastMaterialize an iterable as a list or tuple.objects/protocol.go:SequenceList
2051-2200PyMapping_Length, PyMapping_GetItemString, PyMapping_SetItemString, PyMapping_DelItemString, PyMapping_HasKeyMapping protocol entry points.objects/protocol.go:MappingGetItem
2201-2400PyMapping_Keys, PyMapping_Values, PyMapping_Items, PyMapping_GetOptionalItemMapping view accessors.objects/protocol.go:MappingKeys
2401-2600PyObject_GetIter, PyObject_GetAIter, PyIter_NextIterator protocol: tp_iter dispatch and tp_iternext.objects/protocol.go:GetIter, IterNext
2601-2750PyIter_SendSend a value into a generator or coroutine; maps to tp_iternext or am_send.objects/protocol.go:IterSend
2751-2947PyObject_GetBuffer, PyBuffer_Release, PyObject_CopyData, PyBuffer_FillInfoBuffer protocol: tp_as_buffer->bf_getbuffer.objects/protocol.go:GetBuffer

Reading

PyObject_Call and vectorcall (lines 501 to 700)

cpython 3.14 @ ab2d84fe1023/Objects/abstract.c#L501-700

PyObject_Call(callable, args, kwargs) is the universal call entry point used by the bytecode CALL_INTRINSIC and CALL instructions when the fast paths fail. It checks Py_TPFLAGS_HAVE_VECTORCALL first. When the flag is set and kwargs is NULL or empty, the vectorcall path is taken directly with a C array, avoiding allocation of an args tuple:

PyObject *
PyObject_Call(PyObject *callable, PyObject *args, PyObject *kwargs)
{
ternaryfunc call;
PyTypeObject *tp = Py_TYPE(callable);

if ((tp->tp_flags & Py_TPFLAGS_HAVE_VECTORCALL) &&
PyCFunction_CheckExact(callable)) {
// Fast path: use vectorcall directly.
return _PyObject_VectorcallTstate(tstate, callable, ...);
}
call = tp->tp_call;
if (call == NULL) {
PyErr_Format(PyExc_TypeError,
"'%.200s' object is not callable", tp->tp_name);
return NULL;
}
return (*call)(callable, args, kwargs);
}

_PyObject_FastCallDict converts a C array of arguments plus a kwnames tuple into the vectorcall convention before forwarding. This is the bridge between the old tp_call(args_tuple, kwargs_dict) ABI and the newer vectorcallfunc(callable, args, nargs, kwnames) ABI.

binary_op (lines 901 to 1000)

cpython 3.14 @ ab2d84fe1023/Objects/abstract.c#L901-1000

The engine behind every PyNumber_Add, PyNumber_Subtract, etc. Consults the numeric slot in the left operand's type, then the right operand's. If type(w) is a strict subtype of type(v), the reflected slot on w is tried first to let subclasses override parent arithmetic:

static PyObject *
binary_op1(PyObject *v, PyObject *w, const int op_slot, const char *op_name)
{
binaryfunc slotv = NB_BINOP(Py_TYPE(v)->tp_as_number, op_slot);
binaryfunc slotw = NB_BINOP(Py_TYPE(w)->tp_as_number, op_slot);

// If w's type is a subtype of v's, try w's slot first.
if (slotw != NULL &&
!Py_IS_TYPE(v, Py_TYPE(w)) &&
PyType_IsSubtype(Py_TYPE(w), Py_TYPE(v))) {
PyObject *x = slotw(v, w);
if (x != Py_NotImplemented)
return x;
Py_DECREF(x);
slotw = NULL;
}
if (slotv != NULL) {
PyObject *x = slotv(v, w);
if (x != Py_NotImplemented)
return x;
Py_DECREF(x);
}
if (slotw != NULL) {
PyObject *x = slotw(v, w);
if (x != Py_NotImplemented)
return x;
Py_DECREF(x);
}
PyErr_Format(PyExc_TypeError,
"unsupported operand type(s) for %s: '%.100s' and '%.100s'",
op_name, Py_TYPE(v)->tp_name, Py_TYPE(w)->tp_name);
return NULL;
}

Both NotImplemented results cause a TypeError. The NB_BINOP macro extracts a slot from tp_as_number without dereferencing a NULL sub-struct.

PyNumber_Index (lines 1401 to 1500)

cpython 3.14 @ ab2d84fe1023/Objects/abstract.c#L1401-1500

The __index__ protocol for slice indices. Calls nb_index, checks the result is an exact int (raises TypeError on wrong type), and clamps the value into [PY_SSIZE_T_MIN, PY_SSIZE_T_MAX] with a DeprecationWarning when the value is out of range:

PyObject *
PyNumber_Index(PyObject *item)
{
if (PyLong_Check(item)) {
return Py_NewRef(item);
}
if (!_PyIndex_Check(item)) {
PyErr_Format(PyExc_TypeError,
"'%.200s' object cannot be interpreted as an integer",
Py_TYPE(item)->tp_name);
return NULL;
}
PyObject *result = Py_TYPE(item)->tp_as_number->nb_index(item);
if (!result || PyLong_CheckExact(result))
return result;
if (!PyLong_Check(result)) {
PyErr_Format(PyExc_TypeError,
"__index__ returned non-int (type %.200s)",
Py_TYPE(result)->tp_name);
Py_DECREF(result);
return NULL;
}
/* Issue a deprecation warning for non-exact int results. */
...
return result;
}

PyNumber_AsSsize_t calls PyNumber_Index and then saturates to the Py_ssize_t range, which is how a[2**100] avoids overflow in container indexing.

PySequence_Contains (lines 1800 to 1900)

cpython 3.14 @ ab2d84fe1023/Objects/abstract.c#L1800-1900

The in operator. Tries sq_contains (the __contains__ slot) first. If the slot is absent, falls back to a linear scan: iterate with PyIter_Next and compare each element via PyObject_RichCompareBool(item, value, Py_EQ). This fallback is what makes in work on any iterable even without a custom __contains__:

int
PySequence_Contains(PyObject *seq, PyObject *ob)
{
Py_ssize_t result;
PySequenceMethods *sqm = Py_TYPE(seq)->tp_as_sequence;
if (sqm != NULL && sqm->sq_contains != NULL)
return (*sqm->sq_contains)(seq, ob);
result = _PySequence_IterSearch(seq, ob, PY_ITERSEARCH_CONTAINS);
if (result < 0)
return -1;
return (int)result;
}

_PySequence_IterSearch is also used by list.index and list.count.

gopy mirror

objects/protocol.go. PyObject_Call maps to Call(callable, args, kwargs). Vectorcall is implemented with the same flag check and array-based dispatch. Numeric binary operations delegate to (*Type).NumberMethods. The async iteration helpers (PyObject_GetAIter, PyIter_Send) are present.

CPython 3.14 changes

PyObject_Vectorcall stabilized as public API in 3.9. PyIter_Send added in 3.10. PyMapping_GetOptionalItem added in 3.13. No significant 3.14-specific changes in this file.