Python,我应该基于__eq__实现__ne __()运算符吗?

Fal*_*rri 80 python comparison operators python-datamodel

我有一个类,我想覆盖__eq__()运算符.似乎我应该覆盖__ne__()运算符,但是__ne__基于__eq__这样实现它是否有意义?

class A:
    def __eq__(self, other):
        return self.value == other.value

    def __ne__(self, other):
        return not self.__eq__(other)
Run Code Online (Sandbox Code Playgroud)

或者,Python使用这些运算符的方式是否缺少某些东西,这不是一个好主意?

Aar*_*all 104

Python,我应该__ne__()基于实现运算符__eq__吗?

简答:不.用==而不是__eq__

在Python 3中,默认情况下!=是否定==,因此您甚至不需要编写__ne__文档,并且文档不再以编写文档为主.

一般来说,对于仅限Python 3的代码,除非需要掩盖父实现,例如对于内置对象,否则不要编写代码.

也就是说,请记住Raymond Hettinger的评论:

仅当尚未在超类中定义时,该__ne__方法__eq__才会 自动进行__ne__.所以,如果你继承了内置函数,最好覆盖它们.

如果您需要在Python 2中使用您的代码,请遵循Python 2的建议,它将在Python 3中正常工作.

在Python 2中,Python本身并不会自动实现任何操作 - 因此,您应该__ne__根据==而不是使用来定义__eq__.例如

class A(object):
    def __eq__(self, other):
        return self.value == other.value

    def __ne__(self, other):
        return not self == other # NOT `return not self.__eq__(other)`
Run Code Online (Sandbox Code Playgroud)

见证明

  • __ne__()基于__eq__和实现运算符
  • 根本没有__ne__在Python 2中实现

在下面的演示中提供了错误的行为.

答案很长

Python 2 的文档说:

比较运算符之间没有隐含的关系.事实x==y并非暗示这x!=y是错误的.因此,在定义时__eq__(),还应该定义__ne__()操作符将按预期运行.

所以这意味着如果我们__ne__根据逆的定义来定义__eq__,我们就可以获得一致的行为.

本文档的这一部分已针对Python 3进行了更新:

默认情况下,除非是结果,否则__ne__()委托__eq__()并反转结果NotImplemented.

"什么是新的"部分,我们看到这种行为已经改变:

  • !=现在返回相反的==,除非==返回NotImplemented.

为了实现__ne__,我们更喜欢使用==运算符而不是__eq__直接使用该方法,以便如果self.__eq__(other)子类返回NotImplemented已检查的类型,Python将适当地检查other.__eq__(self) 来自文档:

NotImplemented对象

此类型具有单个值.有一个具有此值的对象.可以通过内置名称访问此对象 NotImplemented.如果数值方法和丰富的比较方法未实现所提供操作数的操作,则它们可能会返回此值.(然后,解释器将尝试反射操作或其他一些后备操作,具体取决于操作员.)其真值是真的.

当给定一个丰富比较运算符,如果他们不相同的类型,Python的检查,如果other是一个子类型,并且如果它具有定义的操作者,它使用other第一的方法(逆为<,<=,>=>).如果NotImplemented返回,使用相反的方法.(它不是检查相同的方法两次.)使用==操作员允许这种逻辑发生.


期望

在语义上,您应该__ne__在检查相等性方面实现,因为类的用户希望以下函数对于A的所有实例都是等效的:

def negation_of_equals(inst1, inst2):
    """always should return same as not_equals(inst1, inst2)"""
    return not inst1 == inst2

def not_equals(inst1, inst2):
    """always should return same as negation_of_equals(inst1, inst2)"""
    return inst1 != inst2
Run Code Online (Sandbox Code Playgroud)

也就是说,上述两个函数应始终返回相同的结果.但这取决于程序员.

__ne__基于以下定义来演示意外行为__eq__:

首先是设置:

class BaseEquatable(object):
    def __init__(self, x):
        self.x = x
    def __eq__(self, other):
        return isinstance(other, BaseEquatable) and self.x == other.x

class ComparableWrong(BaseEquatable):
    def __ne__(self, other):
        return not self.__eq__(other)

class ComparableRight(BaseEquatable):
    def __ne__(self, other):
        return not self == other

class EqMixin(object):
    def __eq__(self, other):
        """override Base __eq__ & bounce to other for __eq__, e.g. 
        if issubclass(type(self), type(other)): # True in this example
        """
        return NotImplemented

class ChildComparableWrong(EqMixin, ComparableWrong):
    """__ne__ the wrong way (__eq__ directly)"""

class ChildComparableRight(EqMixin, ComparableRight):
    """__ne__ the right way (uses ==)"""

class ChildComparablePy3(EqMixin, BaseEquatable):
    """No __ne__, only right in Python 3."""
Run Code Online (Sandbox Code Playgroud)

实例化非等效实例:

right1, right2 = ComparableRight(1), ChildComparableRight(2)
wrong1, wrong2 = ComparableWrong(1), ChildComparableWrong(2)
right_py3_1, right_py3_2 = BaseEquatable(1), ChildComparablePy3(2)
Run Code Online (Sandbox Code Playgroud)

预期行为:

(注意:虽然以下每一个的每一个断言都是等价的,因此在逻辑上多余于它之前的断言,我将它们包括在内,以证明当一个是另一个的子类时,顺序无关紧要.)

这些实例已__ne__实现==:

assert not right1 == right2
assert not right2 == right1
assert right1 != right2
assert right2 != right1
Run Code Online (Sandbox Code Playgroud)

这些在Python 3下测试的实例也可以正常工作:

assert not right_py3_1 == right_py3_2
assert not right_py3_2 == right_py3_1
assert right_py3_1 != right_py3_2
assert right_py3_2 != right_py3_1
Run Code Online (Sandbox Code Playgroud)

并回想一下这些已__ne__实现__eq__- 虽然这是预期的行为,但实现是不正确的:

assert not wrong1 == wrong2         # These are contradicted by the
assert not wrong2 == wrong1         # below unexpected behavior!
Run Code Online (Sandbox Code Playgroud)

意外行为:

请注意,这种比较与上面的比较(not wrong1 == wrong2)相矛盾.

>>> assert wrong1 != wrong2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError
Run Code Online (Sandbox Code Playgroud)

和,

>>> assert wrong2 != wrong1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AssertionError
Run Code Online (Sandbox Code Playgroud)

不要__ne__在Python 2中跳过

有关您不应该__ne__在Python 2中跳过实现的证据,请参阅以下等效对象:

>>> right_py3_1, right_py3_1child = BaseEquatable(1), ChildComparablePy3(1)
>>> right_py3_1 != right_py3_1child # as evaluated in Python 2!
True
Run Code Online (Sandbox Code Playgroud)

以上结果应该是False!

Python 3源码

默认的CPython的实施__ne__typeobject.cobject_richcompare:

    case Py_NE:
        /* By default, __ne__() delegates to __eq__() and inverts the result,
           unless the latter returns NotImplemented. */
        if (self->ob_type->tp_richcompare == NULL) {
            res = Py_NotImplemented;
            Py_INCREF(res);
            break;
        }
        res = (*self->ob_type->tp_richcompare)(self, other, Py_EQ);
        if (res != NULL && res != Py_NotImplemented) {
            int ok = PyObject_IsTrue(res);
            Py_DECREF(res);
            if (ok < 0)
                res = NULL;
            else {
                if (ok)
                    res = Py_False;
                else
                    res = Py_True;
                Py_INCREF(res);
            }
        }
Run Code Online (Sandbox Code Playgroud)

我们在这里看到

但默认__ne__使用__eq__

Python 3 __ne__在C级别的默认实现细节使用__eq__是因为较高级别==(PyObject_RichCompare)效率较低 - 因此它也必须处理NotImplemented.

如果__eq__正确实现,那么否定==也是正确的 - 它允许我们避免在我们的低级实现细节__ne__.

使用==使我们能够保持我们的逻辑低电平一个地方,避免处理NotImplemented__ne__.

人们可能错误地认为==可能会返回NotImplemented.

它实际上使用与默认实现相同的逻辑__eq__,它检查身份(请参阅下面的do_richcompare和我们的证据)

class Foo:
    def __ne__(self, other):
        return NotImplemented
    __eq__ = __ne__

f = Foo()
f2 = Foo()
Run Code Online (Sandbox Code Playgroud)

比较:

>>> f == f
True
>>> f != f
False
>>> f2 == f
False
>>> f2 != f
True
Run Code Online (Sandbox Code Playgroud)

性能

不要相信我的话,让我们看看性能更高:

class CLevel:
    "Use default logic programmed in C"

class HighLevelPython:
    def __ne__(self, other):
        return not self == other

class LowLevelPython:
    def __ne__(self, other):
        equal = self.__eq__(other)
        if equal is NotImplemented:
            return NotImplemented
        return not equal

def c_level():
    cl = CLevel()
    return lambda: cl != cl

def high_level_python():
    hlp = HighLevelPython()
    return lambda: hlp != hlp

def low_level_python():
    llp = LowLevelPython()
    return lambda: llp != llp
Run Code Online (Sandbox Code Playgroud)

我认为这些表现数字不言自明:

>>> import timeit
>>> min(timeit.repeat(c_level()))
0.09377292497083545
>>> min(timeit.repeat(high_level_python()))
0.2654011140111834
>>> min(timeit.repeat(low_level_python()))
0.3378178110579029
Run Code Online (Sandbox Code Playgroud)

当您考虑low_level_python在Python中执行逻辑时,这是有意义的,否则将在C级别上处理.

结论

对于Python 2兼容代码,请使用not self == other实现__ne__.它更多:

  • 正确
  • 简单
  • 高性能

仅在Python 3中,使用C级别的低级否定 - 它更加简单和高效(尽管程序员负责确定它是正确的).

千万不能写在高层次的Python低电平逻辑.

  • 很好的例子!令人惊讶的部分是操作数的顺序_根本不重要,不像一些魔术方法的"右侧"反射.重新迭代我错过的部分(这花费了我很多时间):首先尝试_subclass_的丰富比较方法,无论代码是否具有运算符左侧的超类或子类.这就是为什么你的'a1!= c2`返回'False`--它没有运行`a1 .__ ne__`,而是`c2 .__ ne__`,它否定了_mixin's``_eq__`方法.由于`NotImplemented`是真实的,'not NotImplemented`是'False`. (3认同)
  • 您最近的更新确实成功展示了`not(self == other)`的性能优势,但是没有人争论它不是很快(嗯,比Py2上的任何其他选项都要快)。问题是在某些情况下是“错误”的。Python本身曾经做过“ not(self == other)”,但由于[在存在任意子类的情况下不正确](https://bugs.python.org/issue21408)而发生了变化。最快的错误答案仍然是*错误*。 (2认同)

Dan*_*olo 49

是的,那很好.事实上,文档敦促您定义__ne__何时定义__eq__:

比较运算符之间没有隐含的关系.事实x==y并非暗示这x!=y 是错误的.因此,在定义时 __eq__(),还应该定义__ne__()操作符将按预期运行.

在很多情况下(比如这个),它会像否定结果一样简单__eq__,但并非总是如此.

  • [this](http://stackoverflow.com/a/30676267/916568)是正确的答案(在这里,@ aaron-hall).你引用的文档*不*鼓励你使用`__eq__`实现`__ne__`,只是你实现它. (11认同)
  • 较新的文档(至少对于 3.7,可能更早) `__ne__` 自动委托给 `__eq__` 并且此答案中的引用不再存在于文档中。最重要的是,仅实现“__eq__”并让“__ne__”委托是完全Pythonic的。 (4认同)
  • @guyarad:实际上,由于没有正确授权,Aaron 的回答仍然略有错误;不是将来自一侧的 `NotImplemented` 返回视为在另一侧委托给 `__ne__` 的提示,`not self == other` 是(假设操作数的 `__eq__` 不知道如何比较另一个操作数) 从另一侧隐式委托给 `__eq__`,然后将其反转。对于奇怪的类型,例如 SQLAlchemy ORM 的字段,这 [引起问题](/sf/answers/2504715811/)。 (2认同)

Sha*_*ger 7

仅作记录,一个规范正确且可交叉的Py2 / Py3便携式计算机__ne__看起来像:

import sys

class ...:
    ...
    def __eq__(self, other):
        ...

    if sys.version_info[0] == 2:
        def __ne__(self, other):
            equal = self.__eq__(other)
            return equal if equal is NotImplemented else not equal
Run Code Online (Sandbox Code Playgroud)

这适用于__eq__您可能定义的任何对象:

  • 不像not (self == other),不涉及一些比较烦人/复杂的情况下干扰,其中所涉及的类别之一,并不意味着结果__ne__是一样的结果not__eq__(如SQLAlchemy的的ORM,其中两个__eq____ne__返回特殊的代理对象,没有TrueFalse,并尝试not的结果__eq__将返回False,而不是正确的代理对象)。
  • 不像not self.__eq__(other),这个正确委托给__ne__其他实例的时候self.__eq__回报NotImplementednot self.__eq__(other)将额外错误的,因为NotImplemented是truthy,所以当__eq__不知道如何进行比较,__ne__将返回False,这意味着这两个对象是相等的,而实际上只询问的对象不知道,这意味着默认值不相等)

如果您__eq__不使用NotImplemented退货,则可以正常工作(开销没有意义),如果NotImplemented有时使用退货,则可以正确处理它。而且,Python版本检查意味着如果该类import在Python 3中为-ed ,则将__ne__保持未定义状态,从而可以接替Python的本机高效后备__ne__实现(上述版本的C版本)


为什么需要这个

Python重载规则

为什么要这样做而不是其他解决方案的解释有些奥秘。Python对于重载运算符有一些通用规则,尤其是比较运算符:

  1. (适用于所有运算符)运行时LHS OP RHS,请尝试LHS.__op__(RHS),如果返回NotImplemented,请尝试RHS.__rop__(LHS)。例外:如果RHS是的类的子LHS类,则RHS.__rop__(LHS) 进行测试。在比较操作符的情况下,__eq____ne__是自己的“ROP” S(所以测试顺序__ne__LHS.__ne__(RHS),那么RHS.__ne__(LHS),逆转如果RHS是的一个子类LHS的类)
  2. 除了“交换”运算符的概念外,运算符之间没有隐含的关系。即使是同一类的实例,LHS.__eq__(RHS)返回True也并不意味着LHS.__ne__(RHS)返回False(实际上,甚至不需要运算符返回布尔值; SQLAlchemy之类的ORM故意不这样做,从而允许更具表达性的查询语法)。从Python 3开始,默认__ne__实现的行为方式是这样的,但是它不是契约性的。您可以__ne__采用与严格相反的方式进行覆盖__eq__

这如何适用于比较器过载

因此,当您使运算符重载时,您有两个工作:

  1. 如果您知道如何自己执行该操作,请使用自己的比较知识来进行操作(绝对不要将其隐式或显式委派给该操作的另一端;这样做有可能导致错误和/或无限递归,取决于您的操作方式)
  2. 如果您知道如何自己实现该操作,请始终返回NotImplemented,以便Python可以委派给另一个操作数的实现。

问题所在 not self.__eq__(other)

def __ne__(self, other):
    return not self.__eq__(other)
Run Code Online (Sandbox Code Playgroud)

从不委托给另一方(如果__eq__正确返回,则是不正确的NotImplemented)。当self.__eq__(other)收益NotImplemented(这是“truthy”),你不返回False,这样A() != something_A_knows_nothing_about的回报False,当它应该检查是否something_A_knows_nothing_about知道如何比较的情况下A,如果没有,就应该已经返回True(如果双方都不知道如何自相比之下,它们被认为是不相等的)。如果A.__eq__未正确实现(返回False而不是NotImplemented在其无法识别另一侧时),则从A的角度来看,这是“正确的” ,返回True(因为A认为不相等,所以不相等),但是可能错误的something_A_knows_nothing_about的观点,因为它从未问过something_A_knows_nothing_aboutA() != something_A_knows_nothing_about结束了True,但something_A_knows_nothing_about != A()可能False返回,或其他任何返回值。

问题所在 not self == other

def __ne__(self, other):
    return not self == other
Run Code Online (Sandbox Code Playgroud)

更微妙。对于99%的类,这将是正确的,包括所有__ne__与逻辑倒数的类__eq__。但是not self == other违反了上述两个规则,这意味着对于__ne__ 不是逻辑逆的类__eq__,结果再次是非自反的,因为从未询问过其中一个操作数是否可以实现__ne__,即使另一个操作数不能。最简单的示例是weirdo类,该类False针对所有比较返回,因此A() == Incomparable()A() != Incomparable()两者都返回False。使用正确的实现A.__ne__(一个NotImplemented不知道如何进行比较时返回的关系),该关系是自反的。A() != Incomparable()Incomparable() != A()同意结果(因为在前一种情况下,A.__ne__return NotImplemented,然后Incomparable.__ne__return False,而在后一种情况下,直接Incomparable.__ne__返回False)。但是,当A.__ne__实现为时return not self == otherA() != Incomparable()返回True(因为A.__eq__返回,而不是NotImplemented,然后Incomparable.__eq__返回False,并将其A.__ne__反转为True),而Incomparable() != A()返回False.

您可以在这里看到一个实际的例子。

显然,一类总是返回False两个__eq____ne__是有点怪。但正如前面所提到的,__eq__并且__ne__甚至不需要返回True/ False; SQLAlchemy ORM具有带有比较器的类,这些类返回一个用于构建查询的特殊代理对象,而不是True/ 根本不返回False(如果在布尔上下文中进行评估,它们是“真实的”,但永远不要在这样的上下文中对其进行评估)。

如果无法__ne__正确重载,您破坏该类,如代码所示:

 results = session.query(MyTable).filter(MyTable.fieldname != MyClassWithBadNE())
Run Code Online (Sandbox Code Playgroud)

将起作用(假设SQLAlchemy完全知道如何插入MyClassWithBadNESQL字符串;这可以使用类型适配器来完成,而MyClassWithBadNE无需完全配合),将预期的代理对象传递给filter,而:

 results = session.query(MyTable).filter(MyClassWithBadNE() != MyTable.fieldname)
Run Code Online (Sandbox Code Playgroud)

最终将传递filter一个纯文本False,因为它self == other返回一个代理对象,并且not self == other将真实的代理对象转换为False。希望filter在处理类似的无效参数时引发异常False。虽然我敢肯定,很多人会认为MyTable.fieldname 应该始终在比较的左手边,但事实是,在一般情况下没有程序上的理由来强制执行此操作,正确的泛型__ne__将以两种方式return not self == other起作用,而仅能起作用在一种安排中。

  • +1。对于很久以前忘记为 Python 2 实现 __ne__` 的现有项目,我只是在寻找一个最能模仿 Python 3 行为的 __ne__ 填充程序(不带 __ne__`),以防止现有 Python 的回归3 用户,即使在病态情况下。我针对其他几个类测试了 @AaronHall 的解决方案,其中一些确实很复杂,但它有时不会返回与 Python 3-without-`__ne__` 相同的结果。相比之下,这个 @ShadowRanger/@Maggyero 解决方案的行为始终与 Python 3 完全一样,没有“__ne__”,无论我向它扔什么疯狂的东西。 (2认同)

Mag*_*ero 7

正确__ne__执行

@ShadowRanger 对特殊方法的实现__ne__是正确的:

def __ne__(self, other):
    result = self.__eq__(other)
    if result is not NotImplemented:
        return not result
    return NotImplemented
Run Code Online (Sandbox Code Playgroud)

它也恰好是__ne__ 自 Python 3.4 以来特殊方法的默认实现,如Python 文档中所述

默认情况下,__ne__()__eq__()结果委托给并反转结果,除非它是NotImplemented

另请注意,返回NotImplemented不受支持的操作数的值并非特定于特殊方法__ne__。实际上,所有特殊比较方法1和特殊数值方法2都应返回NotImplemented不受支持的操作数的值,如Python 文档中所述

未实现

这种类型只有一个值。有一个具有此值的对象。这个对象是通过内置的 name 访问的NotImplemented。如果数值方法和富比较方法没有实现对提供的操作数的操作,它们应该返回这个值。(然后解释器将尝试反射操作,或其他一些回退,这取决于操作符。)它的真值是真的。

Python 文档中给出了特殊数字方法的示例:

class MyIntegral(Integral):

    def __add__(self, other):
        if isinstance(other, MyIntegral):
            return do_my_adding_stuff(self, other)
        elif isinstance(other, OtherTypeIKnowAbout):
            return do_my_other_adding_stuff(self, other)
        else:
            return NotImplemented

    def __radd__(self, other):
        if isinstance(other, MyIntegral):
            return do_my_adding_stuff(other, self)
        elif isinstance(other, OtherTypeIKnowAbout):
            return do_my_other_adding_stuff(other, self)
        elif isinstance(other, Integral):
            return int(other) + int(self)
        elif isinstance(other, Real):
            return float(other) + float(self)
        elif isinstance(other, Complex):
            return complex(other) + complex(self)
        else:
            return NotImplemented
Run Code Online (Sandbox Code Playgroud)

1种特殊的比较方法:__lt____le____eq____ne____gt____ge__

2特殊的数字方法:__add__, __sub__, __mul__, __matmul__, __truediv__, __floordiv__, __mod__, __divmod__, __pow__, __lshift__, __rshift__, __and__, __xor__,__or__以及它们的__r*__反射和__i*__就地对应物。

不正确的__ne__实现 #1

@Falmarri 对特殊方法的实现__ne__不正确:

def __ne__(self, other):
    return not self.__eq__(other)
Run Code Online (Sandbox Code Playgroud)

这个实现的问题在于它不会回退到__ne__另一个操作数的特殊方法,因为它从不返回值NotImplemented(表达式not self.__eq__(other)计算为值Trueor False,包括当它的子表达式self.__eq__(other)计算为值时,NotImplemented因为表达式bool(NotImplemented)计算为值True)。值的布尔计算NotImplemented打破了比较运算符和之间的补码关系:!===

class Correct:

    def __ne__(self, other):
        result = self.__eq__(other)
        if result is not NotImplemented:
            return not result
        return NotImplemented


class Incorrect:

    def __ne__(self, other):
        return not self.__eq__(other)


x, y = Correct(), Correct()
assert (x != y) is not (x == y)

x, y = Incorrect(), Incorrect()
assert (x != y) is not (x == y)  # AssertionError
Run Code Online (Sandbox Code Playgroud)

不正确的__ne__实现 #2

@AaronHall 对特殊方法的实现__ne__也是不正确的:

def __ne__(self, other):
    return not self == other
Run Code Online (Sandbox Code Playgroud)

这种实现的问题在于它直接回退到__eq__另一个操作数的特殊方法,绕过另一个操作数的特殊方法__ne__,因为它从不返回值NotImplemented(表达式not self == other回退到__eq__另一个操作数的特殊方法并计算为值TrueFalse)。绕过方法是不正确的,因为该方法可能会产生副作用,例如更新对象的状态:

class Correct:

    def __init__(self):
        self.counter = 0

    def __ne__(self, other):
        self.counter += 1
        result = self.__eq__(other)
        if result is not NotImplemented:
            return not result
        return NotImplemented


class Incorrect:

    def __init__(self):
        self.counter = 0

    def __ne__(self, other):
        self.counter += 1
        return not self == other


x, y = Correct(), Correct()
assert x != y
assert x.counter == y.counter

x, y = Incorrect(), Incorrect()
assert x != y
assert x.counter == y.counter  # AssertionError
Run Code Online (Sandbox Code Playgroud)

了解比较操作

在数学中,二元关系 ř在一组X是一组有序对(的X,  ÿ中)  X 2。声明(X,  ÿ中)  - [R读作“ X- [R -相关于ý ”,并且由表示XRY

集合X上的二元关系R 的性质:

  • [R自反所有当XXXRX
  • [R漫反射的(也称为严格)当对于所有XX,不XRX
  • [R对称的,当对于所有XÿX,如果XRY然后YRX
  • [R反对称的,当对于所有XÿX,如果XRYYRX然后X  =  ý
  • [R传递所有当XÿŽX,如果XRYyRz然后XRZ
  • [RCONNEX(也称为)当对于所有XÿXXRYYRX
  • R是自反的、对称的和可传递的时,R是一个等价关系。 例如,=。然而 ?只是对称的。
  • [R是一个顺序关系[R是自反的,反对称和传递。
    例如, ?和 ?。
  • [R是一个严格顺序关系[R是漫反射的,反对称和传递。
    例如,< 和 >。然而 ?只是不自反。

对集合X上的两个二元关系RS 的运算:

  • 相反[R是二元关系ř Ť  = {(Ý,  X)| xRy } 超过X
  • 补体[R是二元关系¬ [R  = {(X,  ÿ)| 不是xRy } 超过X
  • 工会[R小号是二元关系[R  ? S  = {( xy ) | xRyxSy } 超过X

始终有效的比较关系之间的关系:

  • 2 互补关系:= 和 ? 是彼此的补充;
  • 6 逆向关系:= 是自身的逆向关系,? 是自身的逆,< 和 > 是彼此的逆,而 ? 和 ?是彼此的谈话;
  • 2 工会关系: ? 是联合 < 和 = 吗?是 > 和 = 的并集。

仅对连接订单有效的比较关系之间的关系:

  • 4 互补关系:< 和 ? 是彼此的补码,而 > 和 ? 是彼此的补充。

所以,正确的Python实现比较操作符==!=<><=,以及>=对应于比较关系=,?,<,>,β,γ,上述所有的数学性质和关系应持有。

比较操作x operator y调用__operator__其操作数之一的类的特殊比较方法:

class X:

    def __operator__(self, other):
        # implementation
Run Code Online (Sandbox Code Playgroud)

由于R自反隐含xRx,因此自反比较操作x operator yx == y,x <= yx >= y)或自反特殊比较方法调用x.__operator__(y)x.__eq__(y),x.__le__(y)x.__ge__(y))应计算为值,True如果xy相同,即如果表达式x is y计算为True。由于R是非自反意味着不是xRx,因此非自反比较操作x operator y( x != y, x < yand x > y) 或非自反特殊比较方法调用x.__operator__(y)( x.__ne__(y), x.__lt__(y)and if andx.__gt__(y) ) 应评估为值Falsexy相同,即如果表达式的x is y计算结果为True。自反属性被Python用于比较操作者考虑==和相关联的特殊的比较方法__eq__,但令人惊讶地不被认为是用于比较操作符<=>=和相关联的特殊比较方法__le____ge__和漫反射的属性被Python用于比较操作者考虑!=和相关联的特殊的比较方法__ne__,但令人惊讶的不是考虑的比较操作符<>和相关的特殊比较方法__lt____gt__. 被忽略的比较运算符会引发异常TypeError(以及相关的特殊比较方法返回值NotImplemented),如Python 文档中所述

相等比较(==!=)的默认行为基于对象的身份。因此,相同身份的实例的相等比较导致相等,不同身份的实例的相等比较导致不平等。这种默认行为的动机是希望所有对象都应该是自反的(即x is y隐含x == y)。

默认顺序的比较(<><=,和>=)不设置; 一次尝试引发TypeError。这种默认行为的动机是缺乏与平等类似的不变量。[这是不正确的,因为<=and>=像 一样自反==,并且<>像 一样不自反!=。]

该类object提供了由其所有子类继承的特殊比较方法的默认实现,如Python 文档中所述

object.__lt__(self, other)
object.__le__(self, other)
object.__eq__(self, other)
object.__ne__(self, other)
object.__gt__(self, other)
object.__ge__(self, other)

这些就是所谓的“丰富的比较”方法。运算符符号和方法名的对应关系如下:x<ycalls x.__lt__(y)x<=ycalls x.__le__(y)x==ycalls x.__eq__(y)x!=ycalls x.__ne__(y)x>ycalls x.__gt__(y)x>=y calls x.__ge__(y)

如果富比较方法NotImplemented没有为给定的参数对实现操作,它可能会返回单例。

[…]

这些方法没有交换参数版本(当左参数不支持操作但右参数支持时使用);相反,__lt__()__gt__()是彼此的反映,__le__()并且__ge__()是彼此的反映, __eq__()并且__ne__()是他们自己的反映。如果操作数的类型不同,且右操作数的类型是左操作数类型的直接或间接子类,则右操作数的反射方法优先,否则左操作数的方法优先。不考虑虚拟子类化。

由于R = ( R T ) T,比较xRy等效于反向比较yR T x(在 Python 文档中非正式地命名为“reflected”)。因此,有两种方法可以计算比较操作的结果x operator y:调用x.__operator__(y)or 或y.__operatorT__(x)。Python 使用以下计算策略:

  1. 它调用x.__operator__(y)除非右操作数的类是左操作数的类的后代,在这种情况下它调用y.__operatorT__(x)允许类覆盖其祖先的 converse 特殊比较方法)。
  2. 如果操作数xy不受支持(由返回值指示NotImplemented),它将调用逆特殊比较方法作为第一个回退
  3. 如果操作数xy不受支持(由返回值指示NotImplemented),它会引发异常,TypeError但比较运算符除外,==并且!=分别比较操作数的身份和非身份,xy作为第二个回退(利用==和) 的非自反性!=
  4. 它返回结果。

在 CPython 中,这是用 C 代码实现的,它可以翻译成 Python 代码(名称eq为 for ==nefor !=ltfor <gtfor >lefor<=gefor >=):

def eq(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__eq__(left)
        if result is NotImplemented:
            result = left.__eq__(right)
    else:
        result = left.__eq__(right)
        if result is NotImplemented:
            result = right.__eq__(left)
    if result is NotImplemented:
        result = left is right
    return result
Run Code Online (Sandbox Code Playgroud)
def ne(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__ne__(left)
        if result is NotImplemented:
            result = left.__ne__(right)
    else:
        result = left.__ne__(right)
        if result is NotImplemented:
            result = right.__ne__(left)
    if result is NotImplemented:
        result = left is not right
    return result
Run Code Online (Sandbox Code Playgroud)
def lt(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__gt__(left)
        if result is NotImplemented:
            result = left.__lt__(right)
    else:
        result = left.__lt__(right)
        if result is NotImplemented:
            result = right.__gt__(left)
    if result is NotImplemented:
        raise TypeError(
            f"'<' not supported between instances of '{type(left).__name__}' "
            f"and '{type(right).__name__}'"
        )
    return result
Run Code Online (Sandbox Code Playgroud)
def gt(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__lt__(left)
        if result is NotImplemented:
            result = left.__gt__(right)
    else:
        result = left.__gt__(right)
        if result is NotImplemented:
            result = right.__lt__(left)
    if result is NotImplemented:
        raise TypeError(
            f"'>' not supported between instances of '{type(left).__name__}' "
            f"and '{type(right).__name__}'"
        )
    return result
Run Code Online (Sandbox Code Playgroud)
def le(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__ge__(left)
        if result is NotImplemented:
            result = left.__le__(right)
    else:
        result = left.__le__(right)
        if result is NotImplemented:
            result = right.__ge__(left)
    if result is NotImplemented:
        raise TypeError(
            f"'<=' not supported between instances of '{type(left).__name__}' "
            f"and '{type(right).__name__}'"
        )
    return result
Run Code Online (Sandbox Code Playgroud)
def ge(left, right):
    if type(left) != type(right) and isinstance(right, type(left)):
        result = right.__le__(left)
        if result is NotImplemented:
            result = left.__ge__(right)
    else:
        result = left.__ge__(right)
        if result is NotImplemented:
            result = right.__le__(left)
    if result is NotImplemented:
        raise TypeError(
            f"'>=' not supported between instances of '{type(left).__name__}' "
            f"and '{type(right).__name__}'"
        )
    return result
Run Code Online (Sandbox Code Playgroud)

由于R = ¬(¬ R ),比较xRy等效于比较 ¬( x ¬ Ry )。? 是=的补码,所以特殊方法默认按照支持操作数__ne__的特殊方法__eq__实现,而其他特殊比较方法默认独立实现(事实上?是<和=的并集,而?是的并集>和=被令人惊讶地不被认为是,这意味着当前的特殊方法__le____ge__应用户实现的),如在解释Python文档

默认情况下,__ne__()__eq__()结果委托给并反转结果,除非它是NotImplemented。比较运算符之间没有其他隐含的关系,例如,的真值(x<y or x==y)并不意味着x<=y

在 CPython 中,这是用 C 代码实现的,它可以被翻译成 Python 代码:

def __eq__(self, other):
    return self is other or NotImplemented
Run Code Online (Sandbox Code Playgroud)
def __ne__(self, other):
    result = self.__eq__(other)
    if result is not NotImplemented:
        return not result
    return NotImplemented
Run Code Online (Sandbox Code Playgroud)
def __lt__(self, other):
    return NotImplemented
Run Code Online (Sandbox Code Playgroud)
def __gt__(self, other):
    return NotImplemented
Run Code Online (Sandbox Code Playgroud)
def __le__(self, other):
    return NotImplemented
Run Code Online (Sandbox Code Playgroud)
def __ge__(self, other):
    return NotImplemented
Run Code Online (Sandbox Code Playgroud)

所以默认情况下:

  • 比较操作x operator y引发异常TypeError除了比较运算符==!=对于其分别返回值TrueFalse如果所述操作数xy分别是相同的,并且不相同的,并且这些值FalseTrue以其他方式;
  • 一个特殊的比较方法调用x.__operator__(y)返回值NotImplemented除特殊比较方法__eq____ne__用于其分别返回值TrueFalse如果所述操作数xy分别是相同的,并且不相同的,并且该值NotImplemented,否则。