6

是我还是 python 与以下代码混淆了?我希望__le__被调用a <= ab,而不是__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

我在 python 2.7、3.2 和 pypy 1.9 中得到了相同的行为。

我能做些什么来__le__代替__ge__

4

1 回答 1

9

简短的回答是他们希望允许AB覆盖来自A. Python 不能调用AB.__lt__(a, ab),因为a它可能selfAB方法无效,所以它调用了AB.__gt__(ab, a),这是有效的。

长答案有点复杂。

根据丰富比较运算符的文档:

这些方法没有交换参数版本(当左参数不支持操作但右参数支持时使用);相反,__lt__()__gt__()是彼此的反映,__le__()并且__ge__()是彼此的反映,__eq__()并且__ne__()是自己的反映。

换句话说,x <= ywill cally.__ge__(x)在完全相同的情况下x+y会 call y.__radd__(x)。比较:

>>> 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

根据反射运算符的文档:

调用这些方法来实现二进制算术运算……使用反射(交换)操作数。这些函数只有在左操作数不支持相应操作且操作数属于不同类型时才会被调用……</p>

注意:如果右操作数的类型是左操作数类型的子类,并且该子类为操作提供了反射方法,则该方法将在左操作数的非反射方法之前调用。此行为允许子类覆盖其祖先的操作。

因此,因为XY是 的子类,X所以XY.__radd__优先于X.__add__。同样,因为AB是 的子类A,所以AB.__ge__优先于A.__le__

这可能应该更好地记录下来。要弄清楚,您必须忽略括号“当左参数不支持操作但右参数支持时使用”,猜测您需要查找正常的交换运算符(没有链接,甚至提到, 这里),然后忽略“仅当左操作数不支持相应操作时才调用这些函数”的措辞,并查看与上面的内容相矛盾的“注释”……还要注意文档明确说,“比较运算符之间没有隐含关系”,只有在描述交换案例之前的一段,这暗示了这种关系……</p>

最后,这种情况看起来很奇怪,因为AB不是覆盖__ge__它自己,而是从 继承它B,它对它一无所知A并且与它无关。大概B不打算让它的子类覆盖A的行为。但是,如果B打算将其用作 -A派生类的 mixin,那么它可能正是想要这样的覆盖。无论如何,该规则可能已经足够复杂,而无需深入探讨每种方法在 MRO 中的来源。无论是什么推理,__ge__来自哪里都无关紧要;如果它在子类上,它会被调用。

对于您添加的决赛,问题是“我可以做些什么来__le__代替__ge__??”......好吧,你真的不能,就像你可以X.__add__代替XY.__radd__. 当然,您始终可以实现调用(or AB.__ge__)的 (or ),但是以这样一种方式实现它可能更容易,即它首先与 an作为其另一个参数一起使用。或者,您可以删除继承并找到其他方法来建模您以这种方式建模的任何内容。或者您可以显式调用而不是XY.__radd__A.__le__X.__add__AB.__ge__Aa.__le__(ab)a<=ab. 但是否则,如果您以利用“无隐含关系”的方式设计类来做一些奇怪的事情,那么您会被文档误导,并且必须以某种方式重新设计它们。

于 2012-12-10T11:02:56.853 回答