Is it me or python that is confused with the following code ? I would expect __le__
to be called by a <= ab
, not __ge__
:
#!/usr/bin/env python2
class B(object):
def __ge__(self, other):
print("__ge__ unexpectedly called")
class A(object):
def __le__(self, other):
print("__le__ called")
class AB(A, B):
pass
a = A()
ab = AB()
a <= ab # --> __ge__ unexpectedly called
ab <= a # --> __le__ called
I get the same behavior with python 2.7, 3.2 and pypy 1.9.
What can I do to get __le__
called instead of __ge__
??
The short answer is that they wanted to allow AB
to override the behavior from A
. Python can't call AB.__lt__(a, ab)
, because a
may not be a valid self
for an AB
method, so instead, it calls AB.__gt__(ab, a)
, which is valid.
The long answer is a bit more complicated.
According to the docs for rich comparison operators:
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.
In other words, x <= y
will call y.__ge__(x)
in exactly the same cases where x+y
would call y.__radd__(x)
. To compare:
>>> class X(object):
... def __add__(self, other):
... print('X.add')
>>> class Y(object):
... def __radd__(self, other):
... print('Y.radd')
>>> class XY(X, Y):
... pass
>>> x, xy = X(), XY()
>>> x + xy
Y.radd
According to the docs for reflected operators:
These methods are called to implement the binary arithmetic operations… with reflected (swapped) operands. These functions are only called if the left operand does not support the corresponding operation and the operands are of different types…
Note: 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.
So, because XY
is a subclass of X
, XY.__radd__
gets preference over X.__add__
. And, likewise, because AB
is a subclass of A
, AB.__ge__
gets preference over A.__le__
.
This probably should be documented better. To figure it out, you have to ignore the parenthetical "to be used when the left argument does not support the operation but the right argument does", guess that you need to look up the normal swapped operators (there's no link, or even mention, here), then ignore the wording that says "These functions are only called if the left operand does not support the corresponding operation", and see the "Note", which contradicts what came above… Also notice that the docs explicitly say, "There are no implied relationships among the comparison operators", only a paragraph before describing the swapped cases, which imply exactly such relationships…
Finally, this case seems odd, because AB
, rather than overriding __ge__
itself, just inherited it from B
, which knows nothing about A
and is unrelated to it. Presumably B
didn't intend to have its subclasses override A
's behavior. But if B
were meant to be used as a mixin for A
-derived classes, maybe it would intend exactly such an override. And at any rate, the rule is probably already complicated enough without getting into where each method came from in the MRO. Whatever the reasoning, where the __ge__
comes from is irrelevant; if it's there on the subclass, it gets called.
For your added final, question, "What can I do to get __le__
called instead of __ge__
??"… well, you really can't, any more than you can get X.__add__
called instead of XY.__radd__
. Of course you can always implement an AB.__ge__
(or XY.__radd__
) that calls A.__le__
(or X.__add__
), but it's presumably easier to just implement AB.__ge__
in such a way that it works with an A
as its other argument in the first place. Alternatively, you could remove the inheritance and find some other way to model whatever you were modeling that way. Or you could explicitly call a.__le__(ab)
instead of a<=ab
. But otherwise, if you designed your classes in a way that takes advantage of the "no implied relationships" to do something weird, you were misled by the docs, and will have to redesign them somehow.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With