3

I can't seem to find the actual language specification on how value comparisons have to be done by a complying implementation (e.g. CPython). Consider the simple case

a == b

The reference says that this is done using the “rich comparison” methods, in this case __eq__(). Neither the Data Model nor the reference on Value Comparison Expressions seem to specify in detail, what else a Python implementation is required/allowed to do, especially if the type(a) signals that it does not implement __eq__ by explicitly returning NotImplemented.

This answer plausibly suggests that for any a == b, CPython

  1. calls b.__eq__(a)(!) if b is a subclass of a. This makes intuitive sense, as it allows the subclass to overload all comparisons between values of itself and it's parent class (therefor also implying symmetry for both the parent and the subclass).
  2. if the above fails, calls a.__eq__(b)
  3. if the above fails, calls b.__eq__(a) if that hasn't happened yet;
  4. falls back to identity comparison

AFAICS this order makes sense, also given Python's loose sense of symmetry, reflexivity, variance and the like. My question is: Does the language guarantee that for any a == b (or other value comparisons), a complying implementation will execute the comparison in the way described above?

user2722968
  • 13,636
  • 2
  • 46
  • 67
  • `PyObject_RichCompare` is a part of stable C ABI, and so `do_richcompare` should retain stability. `do_richcompare` defines how comparison expression is executed, and it tries options in order you have specified ([source code here](https://github.com/python/cpython/blob/main/Objects/object.c#L764), it's easy to follow even if you don't know C). `PyObject_RichCompare` documented as stable [here](https://docs.python.org/3/c-api/object.html#c.PyObject_RichCompare). Nice question - I was surprised to see that such basic information important for many developers is missing from docs. – STerliakov Jul 20 '23 at 12:12
  • 1
    @SUTerliakov-supportsstrike, yeah, I'm aware of the implementation and it's stability. Since the implementation is far more complex than what the docs would suggest, I'm specifically asking if **any** implementation is **required by the language reference** to behave in such way, or if this is just a CPython's way of implementing what is otherwise left ambiguous. – user2722968 Jul 20 '23 at 12:51
  • 1
    Yeah, I saw the [tag:language-lawyer] tag, just sharing a bit context for CPython specifically - it *should* be guaranteed to remain the same there (though, speaking strictly, being part of stable ABI does not imply impossible behavioural changes - if CPython decides to e.g. special-case `None` comparison, it's free to do so). – STerliakov Jul 20 '23 at 12:54
  • It's good to hash out issues like this, but I really think it should result in improvements to the [prior question](https://stackoverflow.com/questions/3588776/) rather than a separate Q&A. – Karl Knechtel Jul 22 '23 at 04:51

1 Answers1

3

The said behaviors are well documented in the rich comparison methods section of Data Model:

x==y calls x.__eq__(y) (behavior #2)

and:

There are no swapped-argument versions of these methods (to be used when the left argument does not support the operation but the right argument does); rather, __lt__() and __gt__() are each other’s reflection, __le__() and __ge__() are each other’s reflection, and __eq__() and __ne__() are their own reflection. (behavior #3) If the operands are of different types, and right operand’s type is a direct or indirect subclass of the left operand’s type, the reflected method of the right operand has priority, otherwise the left operand’s method has priority. (behavior #1)

and finally:

By default, object implements __eq__() by using is, returning NotImplemented in the case of a false comparison: True if x is y else NotImplemented. (behavior #4)

Although in the documentation there are no numberings to explicitly spell out the order of the behaviors above, it is logically inferred:

  • "x==y calls x.__eq__(y)", making it a tentative behavior #1.
  • "__eq__() and __ne__() are their own reflection", so if x.__eq__(y) fails, then y.__eq__(x) is called, making this a tenative behavior #2.
  • "If the operands are of different types, and right operand’s type is a direct or indirect subclass of the left operand’s type, the reflected method of the right operand has priority", so when the right operand is a subclass of the left, this condition overrides the aforementioned two behaviors and calls y.__eq__(x), making it the actual behavior #1, demoting the other two to #2 and #3.
  • "By default, object implements __eq__() by using is", so if all else fails, it would fall back to the default behavior of object's implementation of __eq__() using identity comparison, making it behavior #4.
blhsing
  • 91,368
  • 6
  • 71
  • 106
  • The link you gave is literally the same I used in the question. The bottom part of the answer ("straightforwardly implied") is the whole point: Is this implied, or is this part of the language specification? – user2722968 Jul 21 '23 at 22:12
  • No the lilnk is not literally the same as the one you used in the question. My link points to the exact section where the behaviors are documented, which I thought you missed because you only linked to the entire chapter. And this is the official documentation we're talking about, so why do you think that this is not part of the language specifications? The order of the behaviors are logically inferred by the wordings used to describe them, with no ambiguity. One does not need a formal language specification for such logics to be clearly described. I've updated my answer with such wordings. – blhsing Jul 22 '23 at 02:32
  • 1
    Frankly I don't see how much clearer than that section of the documentation you expect from the Python editors without making it downright unfriendly to the average developers. – blhsing Jul 22 '23 at 02:36