[issue22052] Comparison operators called in reverse order for subclasses with no override.
New submission from Mark Dickinson: As reported in a StackOverflow question [1]: the order in which the special comparison methods are called seems to be contradictory to the docs [2]. In the following snippet, __eq__ is called with reversed operands first:
class A: ... def __eq__(self, other): ... print(type(self), type(other)) ... return True ... class B(A): ... pass ... A() == B()
True
However, the docs note that:
"""If the right operand’s type is a subclass of the left operand’s type and that subclass provides the reflected method for the operation, this method will be called before the left operand’s non-reflected method. This behavior allows subclasses to override their ancestors’ operations."""
... which suggests that this reversal should only happen when the subclass B *overrides* A's definition of __eq__ (and indeed that's the usual behaviour for arithmetic operations like __add__).
Looking more closely, that statement in the docs is in the 'numeric-types' section, so it's not clear that its rules should apply to the comparison operators. But either way, some doc clarification could be useful.
[1] http://stackoverflow.com/q/24919375/270986
[2] https://docs.python.org/3.5/reference/datamodel.html#emulating-numeric-types
----------
assignee: docs@python
components: Documentation
messages: 223778
nosy: docs@python, mark.dickinson
priority: normal
severity: normal
status: open
title: Comparison operators called in reverse order for subclasses with no override.
versions: Python 2.7, Python 3.4, Python 3.5
_______________________________________
Python tracker
R. David Murray added the comment:
"the subclass provides" doesn't actually imply anything about overriding, I think. For __eq__, though, that means that *every* class "provides" it. Indeed, I've always thought of the rule as "the subclass goes first" with no qualification, but that's only true for __eq__.
It looks like the "subclass goes first" note is missing from the __eq__ section.
But see issue 21408. I find it hard to reason about this algorithm, so I could be completely wrong :)
----------
nosy: +r.david.murray
_______________________________________
Python tracker
Mark Dickinson added the comment:
"the subclass provides" doesn't actually imply anything about overriding, I think.
Yes, that was the thrust of one of the SO answers. Unfortunately, that explanation doesn't work for arithmetic operators, though: there an explicit override is necessary. Here's another example, partly to get away from the extra complication of __eq__ being its own inverse. After:
class A(object):
def __lt__(self, other): return True
def __gt__(self, other): return False
def __add__(self, other): return 1729
def __radd__(self, other): return 42
class B(A): pass
we get:
>>> A() + B()
1729
>>> A() < B()
False
So the addition is calling the usual __add__ method first (the special exception in the docs doesn't apply: while B *is* a subclass of A, it doesn't *override* A's __radd__ method). But the comparison is (surprisingly) calling the __gt__ method first.
So we've got two different rules being followed: one for arithmetic operators, and a different one for comparisons.
This isn't a big deal behaviour-wise: I'm certainly not advocating a behaviour change here. But it would be nice to document it.
----------
_______________________________________
Python tracker
R. David Murray added the comment:
Ah yes. I remember there being a discussion somewhere about the differences between comparison operator inverses and the arithmetic 'r' methods, but I can't find it at the moment. I *thought* there was a full discussion of the logic involved in these cases, but I can't find that either. We need one somewhere that we can crosslink to if it doesn't already exist.
----------
_______________________________________
Python tracker
Martin Panter added the comment:
I have included some rules about the priority for calling reflected operator methods in my patch to Issue 4395
----------
nosy: +vadmium
_______________________________________
Python tracker
Martin Panter added the comment:
My patch was committed for Python 3.4+. The priority of the comparator methods is now documented at the end of https://docs.python.org/dev/reference/datamodel.html#richcmpfuncs. Perhaps all that is left to do here is to apply similar changes to the Python 2 documentation.
----------
_______________________________________
Python tracker
Martin Panter added the comment:
Does anyone know enough about Python 2 to propose a fix? I don’t know enough about object classes versus “instance” classes, and potential interference of the __cmp__() method. In Python 2 the order seems to depend on the class type:
(<__main__.A instance at 0x7f730d37f5f0>, <__main__.B instance at 0x7f730d37f518>)
(<__main__.B object at 0x7f730d37dc10>, <__main__.A object at 0x7f730d37d110>)
Or perhaps we should just close this now and forget about Python 2 ;)
----------
stage: -> needs patch
versions: -Python 3.4, Python 3.5
_______________________________________
Python tracker
Mark Dickinson added the comment:
For Python 2, I think the most we should do is document the behaviour somewhere; changing it in a bugfix release seems both unnecessary and potentially risky.
----------
_______________________________________
Python tracker
Mark Dickinson added the comment:
the most we should do is document the behaviour somewhere
And indeed, perhaps this issue counts as sufficient documentation...
----------
_______________________________________
Python tracker
Serhiy Storchaka
participants (4)
-
Mark Dickinson
-
Martin Panter
-
R. David Murray
-
Serhiy Storchaka