New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comparison operators called in reverse order for subclasses with no override. #66251
Comments
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()
<class '__main__.B'> <class '__main__.A'>
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 |
"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 bpo-21408. I find it hard to reason about this algorithm, so I could be completely wrong :) |
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. |
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. |
I have included some rules about the priority for calling reflected operator methods in my patch to bpo-4395 |
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. |
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>) Or perhaps we should just close this now and forget about Python 2 ;) |
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. |
And indeed, perhaps this issue counts as sufficient documentation... |
Python 2.7 is no longer supported. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: