Is it me or python that is confused with the following code ? I would expect
__le__ to be called by
a <= ab, not
#!/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
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,
__gt__()are each other’s reflection,
__ge__()are each other’s reflection, 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.
XY is a subclass of
XY.__radd__ gets preference over
X.__add__. And, likewise, because
AB is a subclass of
AB.__ge__ gets preference over
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
XY.__radd__) that calls
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.