According to the object.__eq__() documentation, the default (that is, in the object class) implementation for == is as follows:
True if x is y else NotImplemented
Still following the documentation for NotImplemented, I inferred that NotImplemented implies that the Python runtime will try the comparison the other way around. That is try y.__eq__(x) if x.__eq__(y) returns NotImplemented (in the case of the == operator).
Now, the following code prints False and True in python 3.9:
class A:
pass
print(A() == A())
print(bool(NotImplemented))
So my question is the following: where does the documentation mention the special behavior of NotImplemented in the context of __eq__ ?
PS : I found an answer in CPython source code but I guess that this must/should be somewhere in the documentation.
No; that is the default implementation of
__eq__.==, being an operator, cannot be implemented in classes.Python's implementation of operators is cooperative. There is hard-coded logic that uses the dunder methods to figure out what should happen, and possibly falls back on a default. This logic is outside of any class.
You can see another example with the built-in
len: a class can return whatever it likes from its__len__method, and you can in principle call it directly and get a value of any type. However, this does not properly implement the protocol, andlenwill complain when it doesn't get a non-negative integer back. There is not any class which contains that type-checking and value-checking logic. It is external.NotImplementedis just an object. It is not syntax. It does not have any special behavior, and in Python, simply returning a value does not trigger special behavior besides that the value is returned.The external code for binary operators will try to look for the matching
__op__, and try to look for the matching__rop__if__op__didn't work. At this point,NotImplementedis not an acceptable answer (it is a sentinel that exists specifically for this purpose, becauseNoneis an acceptable answer). In general, if the answer so far is stillNotImplemented, then the external code willraise NotImplementedError.As a special case, objects that don't provide their own comparison (i.e., the default from
objectis used for__eq__or__ne__) will compare as "not equal" unless they are identical. The C implementation repeats the identity check (in case a class explicitly defines__eq__or__ne__toreturn NotImplementeddirectly, I guess). This is because it is considered sensible to give this result, and obnoxious to make==fail all the time when there is a sensible default.However, the two objects are still not orderable without explicit logic, since there isn't a reasonable default. (You could compare the pointer values, but they're arbitrary and don't have anything to do with the Python logic that got you to that point; so ordering things that way isn't realistically useful for writing Python code.) So, for example,
x < ywill raise aTypeErrorif the comparison logic isn't provided. (It does this even ifx is y; you could reasonably say that<=and>=should be true in this case, and<and>should be false, but it makes things too complicated and is not very useful.)Well, yes;
NotImplementedis an object, so it's truthy by default; and it doesn't represent a numeric value, and isn't a container, so there's no reason for it to be falsy.However, that also doesn't tell us anything useful. We don't care about the truthiness of
NotImplementedhere, and it isn't used that way in the Python implementation. It is just a sentinel value.Nowhere, because it isn't a behavior of
NotImplemented, as explained above.Okay, but that leaves underlying question: where does the documentation explain what the
==operator does by default?Answer: because we are talking about an operator, and not about a method, it's not in the section about dunder methods. It's in section 6, which talks about expressions. Specifically, 6.10.1. Value comparisons: