在Python中为什么/什么时候`x == y`调用`y .__ eq __(x)`?

Sin*_*ned 37 python comparison operator-overloading

Python文档清楚地说明了x==y调用x.__eq__(y).然而,似乎在许多情况下,情况正好相反.它记录了何时或为何发生这种情况,以及如何确定我的对象__cmp____eq__方法是否会被调用.

编辑:只是为了澄清,我知道这__eq__是在优先考虑__cmp__,但我不清楚为什么y.__eq__(x)被优先调用x.__eq__(y),当后者是文档状态将发生.

>>> class TestCmp(object):
...     def __cmp__(self, other):
...         print "__cmp__ got called"
...         return 0
... 
>>> class TestEq(object):
...     def __eq__(self, other):
...         print "__eq__ got called"
...         return True
... 
>>> tc = TestCmp()
>>> te = TestEq()
>>> 
>>> 1 == tc
__cmp__ got called
True
>>> tc == 1
__cmp__ got called
True
>>> 
>>> 1 == te
__eq__ got called
True
>>> te == 1
__eq__ got called
True
>>> 
>>> class TestStrCmp(str):
...     def __new__(cls, value):
...         return str.__new__(cls, value)
...     
...     def __cmp__(self, other):
...         print "__cmp__ got called"
...         return 0
... 
>>> class TestStrEq(str):
...     def __new__(cls, value):
...         return str.__new__(cls, value)
...     
...     def __eq__(self, other):
...         print "__eq__ got called"
...         return True
... 
>>> tsc = TestStrCmp("a")
>>> tse = TestStrEq("a")
>>> 
>>> "b" == tsc
False
>>> tsc == "b"
False
>>> 
>>> "b" == tse
__eq__ got called
True
>>> tse == "b"
__eq__ got called
True
Run Code Online (Sandbox Code Playgroud)

编辑:从Mark Dickinson的回答和评论中可以看出:

  1. 丰富的比较覆盖 __cmp__
  2. __eq__是它自己的__rop__,以它的__op__(和类似的__lt__,__ge__等等)
  3. 如果左对象是内置类或新样式类,右边是它的子类,则__rop__在左对象之前尝试使用右对象__op__

这解释了TestStrCmp示例中的行为. TestStrCmp是的一个子类str,但没有实现自己的__eq__这样__eq__str在这两种情况下的优先级(即tsc == "b"调用b.__eq__(tsc)作为__rop__,因为规则1).

TestStrEq示例中,tse.__eq__在两个实例中都被调用,因为TestStrEq它是子类,str因此优先调用它.

TestEq示例中,TestEq实现__eq__并且两次都int不会__eq__被调用(规则1).

但我仍然不明白第一个例子TestCmp. tc不是一个子类,int所以1.__cmp__(tc)应该调用AFAICT ,但不是.

Mar*_*son 31

您缺少通常行为的关键异常:当右侧操作数是左侧操作数的类的子类的实例时,首先调用右侧操作数的特殊方法.

请参阅以下文档:

http://docs.python.org/reference/datamodel.html#coercion-rules

特别是以下两段:

对于对象xy,首先 x.__op__(y)是尝试.如果没有实现或返回 NotImplemented,y.__rop__(x)则尝试.如果还没有实现或返回NotImplemented,则引发TypeError异常.但请参阅以下异常:

前一项的异常:如果左操作数是内置类型或新样式类的实例,并且右操作数是该类型或类的正确子类的实例并覆盖基类的 __rop__()方法,则右操作数的__rop__()方法在左操作数的__op__() 方法之前尝试.

  • 同意你没有使用任何`__rop__`方法.比较方法在这方面是特殊的:`__eq__`是它自己的反转,因此对`__op__`和`__rop__`都读`__eq__`.(类似地,`__ne__`是它自己的反转,`__ le__`与`__ge__`相反,等等.)其他人之前评论过(正确的,IMO)文档可以在这里使用一些工作.我几乎可以肯定`__rop__`方法不会被弃用! (4认同)
  • 请参见http://docs.python.org/reference/datamodel.html#object.__lt__.第四段开头:"没有这些方法的交换参数版本[...]而是,`__lt __()`和`__gt __()`是彼此的反射,`__ le __()`和`__ge __()`是彼此的反思,而`__eq __()`和`__ne __()`是他们自己的反思." 或者您是否要求提供证据证明`__rop__`方法不被弃用? (3认同)
  • 不,我认为是对的:`__lt__`是`__gt__`的`__rop__`.没有逻辑上的否定; 只是颠倒了论点.翻译是:`x .__ lt __(y)`<=>`x <y` <=>`y> x` <=>`y .__ gt __(x)`. (2认同)
  • `__cmp__`的规则(特别是与丰富的比较相结合)确实是毛茸茸的,我认为它们甚至没有在任何地方正确记录,除了在源中.(PyObject_RichCompare的对象/ object.c文件是看的主要场所,如果你觉得倾斜的.)在`1 == tc`例如,双方都没有实现`__eq__`,所以我们依傍`__cmp__`.`int .__ cmp__`只能处理与其他整数的比较,所以它被忽略(甚至不被调用),而你的`TestCmp .__ cmp__`方法被调用.在py3k中摆脱`__cmp__`是一件非常好的事情.:) (2认同)

Dan*_*umb 6

实际上,在文档中,它指出:

__cmp__如果没有定义丰富的比较(见上文),[ c]通过比较操作进行调整.

__eq__是一种丰富的比较方法,在这种情况下TestCmp,没有定义,因此调用__cmp__