[issue28785] Clarify the behavior of NotImplemented

New submission from Max: Currently, there's no clear statement as to what exactly the fallback is in case `__eq__` returns `NotImplemented`. It would be good to clarify the behavior of `NotImplemented`; at least for `__eq__`, but perhaps also other rich comparison methods. For example: "When `NotImplemented` is returned from a rich comparison method, the interpreter behaves as if the rich comparison method was not defined in the first place." See http://stackoverflow.com/questions/40780004/returning-notimplemented-from-eq for more discussion. ---------- assignee: docs@python components: Documentation messages: 281616 nosy: docs@python, max priority: normal severity: normal status: open title: Clarify the behavior of NotImplemented type: enhancement versions: Python 3.6 _______________________________________ Python tracker <report@bugs.python.org> <http://bugs.python.org/issue28785> _______________________________________

Martin Panter added the comment: The documentation of this that comes to mind is spread over <https://docs.python.org/3.6/reference/datamodel.html#object.__eq__> and <https://docs.python.org/3.6/reference/expressions.html#value-comparisons>. My understanding of how it works in general is that NotImplemented is just a simple object like None. It doesn’t have any special behaviour on its own. Similarly, if you call a special method like __eq__() manually, there is no fallback, you just get the NotImplemented object. The interpreter calls special methods like __eq__() when evaluating expressions, but the specific behaviour and how NotImplemented is interpreted varies subtly. To evaluate a == b, if b’s class is a strict subclass of a’s class, b.__eq__(a) is tried first, otherwise a.__eq__(b) is tried first. If that call returns NotImplemented, the first fallback is to try the call. Failing that, the final fallback is the default behaviour for object() instances. To evaluate a != b, there is another set of fallbacks in the chain, which is to try “not a.__eq__(b)”. So for __eq__(), the fallback depends on the class hierarchy, on whether a reversed call has already been made, and on whether you are evaluating == or !=. The documentation for comparison operations was cleaned up a while ago (Issue 12067). But there is probably more room for improvement. I think the best thing would be to document the above sort of behaviour as being directly associated with operations like as == and !=, and only indirectly associated with the NotImplemented object and the __eq__() method. ---------- nosy: +martin.panter _______________________________________ Python tracker <report@bugs.python.org> <http://bugs.python.org/issue28785> _______________________________________

Max added the comment: Martin - what you suggest is precisely what I had in mind (but didn't phrase it as well):
to document the above sort of behaviour as being directly associated with operations like as == and !=, and only indirectly associated with the NotImplemented object and the __eq__() method
Also a minor typo: you meant "If that call returns NotImplemented, the first fallback is to try the *reverse* call." ---------- _______________________________________ Python tracker <report@bugs.python.org> <http://bugs.python.org/issue28785> _______________________________________

Martin Panter added the comment: Correct, I meant to say the first fallback is the other call. BTW your suggested text might hold for __eq__(), but for __ne__(), returning NotImplemented seems to bypass the “not a.__eq__(b)” fallback. ---------- _______________________________________ Python tracker <report@bugs.python.org> <http://bugs.python.org/issue28785> _______________________________________

Changes by Martin Panter <vadmium+py@gmail.com>: ---------- title: Clarify the behavior of NotImplemented -> Clarify the behavior of __eq__() returning NotImplemented _______________________________________ Python tracker <report@bugs.python.org> <http://bugs.python.org/issue28785> _______________________________________
participants (2)
-
Martin Panter
-
Max