Skip to main content

Objects/object.c (part 3)

Source:

cpython 3.14 @ ab2d84fe1023/Objects/object.c

This annotation covers comparison and truth-testing. See objects_object2_detail for PyObject_Repr, PyObject_Str, PyObject_Format, and PyObject_Bytes.

Map

LinesSymbolRole
1-80PyObject_RichCompareCompare two objects with a relational operator
81-160PyObject_RichCompareBoolCompare and return C int
161-240PyObject_HashCompute or retrieve the hash of an object
241-320PyObject_IsTrueTest truth value (calls __bool__ or __len__)
321-500PyObject_NotNegate truth value; used by not operator

Reading

PyObject_RichCompare

// CPython: Objects/object.c:680 PyObject_RichCompare
PyObject *
PyObject_RichCompare(PyObject *v, PyObject *w, int op)
{
PyObject *res = do_richcompare(v, w, op);
if (res == Py_NotImplemented) {
/* Swap and try the reflected operation */
Py_DECREF(res);
res = do_richcompare(w, v, _Py_SwappedOp[op]);
}
return res;
}

static PyObject *
do_richcompare(PyObject *v, PyObject *w, int op)
{
/* If types differ and w's type is a subtype, give w priority */
if (!Py_IS_TYPE(v, Py_TYPE(w)) &&
PyType_IsSubtype(Py_TYPE(w), Py_TYPE(v)) &&
Py_TYPE(w)->tp_richcompare != NULL) {
res = Py_TYPE(w)->tp_richcompare(w, v, _Py_SwappedOp[op]);
if (res != Py_NotImplemented) return res;
Py_DECREF(res);
}
if (Py_TYPE(v)->tp_richcompare != NULL) {
res = Py_TYPE(v)->tp_richcompare(v, w, op);
if (res != Py_NotImplemented) return res;
Py_DECREF(res);
}
/* Final fallback: identity comparison for == and != */
...
}

Subtypes are tried first. The reflected operation is tried if the left side returns NotImplemented. This is why MyInt() > 5 can work even if only MyInt.__lt__ is defined.

PyObject_Hash

// CPython: Objects/object.c:780 PyObject_Hash
Py_hash_t
PyObject_Hash(PyObject *v)
{
if (Py_TYPE(v)->tp_hash != NULL)
return (*Py_TYPE(v)->tp_hash)(v);
if (Py_TYPE(v)->tp_richcompare != NULL) {
/* Has __eq__ but no __hash__: unhashable */
PyErr_Format(PyExc_TypeError, "unhashable type: '%.200s'",
Py_TYPE(v)->tp_name);
return -1;
}
/* No __eq__ either: use identity hash */
return _Py_HashPointer(v);
}

If a type defines __eq__ without __hash__, it is unhashable by default. This prevents dict from silently using identity-based hashing for types that define equality by value.

PyObject_IsTrue

// CPython: Objects/object.c:1440 PyObject_IsTrue
int
PyObject_IsTrue(PyObject *v)
{
Py_ssize_t res;
if (v == Py_True) return 1;
if (v == Py_False) return 0;
if (v == Py_None) return 0;
else if (Py_TYPE(v)->tp_as_number != NULL &&
Py_TYPE(v)->tp_as_number->nb_bool != NULL)
res = (*Py_TYPE(v)->tp_as_number->nb_bool)(v);
else if (Py_TYPE(v)->tp_as_mapping != NULL &&
Py_TYPE(v)->tp_as_mapping->mp_length != NULL)
res = (*Py_TYPE(v)->tp_as_mapping->mp_length)(v);
else if (Py_TYPE(v)->tp_as_sequence != NULL &&
Py_TYPE(v)->tp_as_sequence->sq_length != NULL)
res = (*Py_TYPE(v)->tp_as_sequence->sq_length)(v);
else
return 1; /* everything else is True */
return (res > 0) ? 1 : (res == 0) ? 0 : -1;
}

Truth testing priority: __bool__ > __len__ > always-true. An empty list is false because len([]) == 0. A custom class without __bool__ or __len__ is always truthy.

gopy notes

PyObject_RichCompare is objects.RichCompare in objects/object.go. The reflected-op table is objects.SwappedOp. PyObject_Hash calls objects.Hash. PyObject_IsTrue is objects.IsTrue, checking objects.BoolHook, objects.LenHook in order.