[Python-Dev] Definition of equality check behavior

Tim Peters tim.peters at gmail.com
Tue May 7 21:23:35 EDT 2019


[Jordan Adler <jordan.m.adler at gmail.com>]
> Through the course of work on the future polyfills that mimic the behavior
> of Py3 builtins across versions of Python, we've discovered that the
> equality check behavior of at least some builtin types do not match the
> documented core data model.
>
> Specifically, a comparison between a primitive (int, str, float were tested)
> and an object of a different type always return False, instead of raising
> a NotImplementedError.  Consider `1 == '1'` as a test case.
>
> Should the data model be adjusted to declare that primitive types are
> expected to fallback to False, or should the cpython primitive type's
> __eq__ implementation fallback to raise NotImplementedError?

Nope ;-)  This isn't a "data model" issue.  Look instead at the
Standard Library manual's section on Built-In Types, under heading
Comparisons:

"""
Objects of different types, except different numeric types, never
compare equal. ...
The <, <=, > and >= operators will raise a TypeError exception when
comparing a complex number with another built-in numeric type, when
the objects are of different types that cannot be compared, or in
other cases where there is no defined ordering.
"""

It's not an object's responsibility to arrange for that.  It's done
for them by default, and objects only need to supply their own rich
comparison methods if they don't want the defaults.  For example, when
comparing an int with another type, all the int rich comparison
methods _do_ return NotImplemented:

>>> f = 4
>>> f.__eq__("abc")
NotImplemented

It's at a higher level that comparison logic says "OK, I gave both
comparands a chance, and they both returned NotImplemented.  So one
last chance (from object.c's do_richcompare())":

    /* 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;
    }

Then the Py_EQ case of that delivers:

>>> f == "abc"
False

and the Py_NE case:

>>> f != "abc"
True

despite that (or because of that ;-) ):

>>> f.__eq__("abc")
NotImplemented
>>> "abc".__eq__(f)
NotImplemented

Note that there's nothing special about builtin types here.  All types
are treated alike.


More information about the Python-Dev mailing list