This behavior can be seen in the implementation of object.c:
/* Perform a rich comparison, raising TypeError when the requested comparison
operator is not supported. */
static PyObject *
do_richcompare(PyObject *v, PyObject *w, int op)
{
// (omitted some code for brevity)
/* 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,
"'%s' not supported between instances of '%.100s' and '%.100s'",
opstrings[op],
v->ob_type->tp_name,
w->ob_type->tp_name);
return NULL;
}
Py_INCREF(res);
return res;
}
Looking at the commit that introduced this change it seems there was an intentional decision to only support equality and not support comparison for objects of different types.
To quote the summary lines from the commit message:
Restructure comparison dramatically. There is no longer a default
ordering between objects; there is only a default equality test
(defined by an object being equal to itself only).
...whereafter the message goes on to describe rationale (and some of the drawbacks to this change).