【问题标题】:Should __ne__ be implemented as the negation of __eq__?__ne__ 是否应该作为 __eq__ 的否定来实现?
【发布时间】:2011-05-20 03:07:32
【问题描述】:

我有一个类,我想覆盖 __eq__ 方法。我也应该重写 __ne__ 方法似乎是有道理的。我应该将__ne__ 实现为__eq__ 的否定,还是一个坏主意?

class A:

    def __init__(self, state):
        self.state = state

    def __eq__(self, other):
        return self.state == other.state

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

【问题讨论】:

    标签: python comparison operators inequality python-datamodel


    【解决方案1】:

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

    简短回答:不要实现它,但如果必须,请使用==,而不是__eq__

    在Python 3中,!=默认是==的否定,所以你甚至不需要写__ne__,文档也不再固执己见。

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

    也就是说,记住Raymond Hettinger's comment:

    __ne__ 方法自动从__eq__ 遵循,仅当 __ne__ 尚未在超类中定义。所以,如果你是 从内置继承,最好覆盖两者。

    如果您需要您的代码在 Python 2 中工作,请遵循 Python 2 的建议,它可以在 Python 3 中正常工作。

    在 Python 2 中,Python 本身不会自动实现对另一个的任何操作 - 因此,您应该根据 == 而不是 __eq__ 定义 __ne__。 例如。

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

    查看证明

    • 基于__eq__
    • 实现__ne__()操作符
    • 根本没有在 Python 2 中实现 __ne__

    在下面的演示中提供了不正确的行为。

    长答案

    Python 2 的 documentation 表示:

    比较运算符之间没有隐含的关系。这 x==y 的真实性并不意味着 x!=y 是错误的。因此,当 定义__eq__(),还应该定义__ne__(),这样 运算符将按预期运行。

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

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

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

    "what's new" section 中,我们看到这种行为发生了变化:

    • != 现在返回与 == 相反的结果,除非 == 返回 NotImplemented

    为了实现__ne__,我们更喜欢使用==操作符而不是直接使用__eq__方法,这样如果子类的self.__eq__(other)返回NotImplemented的类型选中,Python 会适当检查other.__eq__(self) From the documentation:

    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
    

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

    基于__eq__定义__ne__时的意外行为演示:

    首先设置:

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

    实例化非等效实例:

    right1, right2 = ComparableRight(1), ChildComparableRight(2)
    wrong1, wrong2 = ComparableWrong(1), ChildComparableWrong(2)
    right_py3_1, right_py3_2 = BaseEquatable(1), ChildComparablePy3(2)
    

    预期行为:

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

    这些实例使用== 实现了__ne__

    assert not right1 == right2
    assert not right2 == right1
    assert right1 != right2
    assert right2 != right1
    

    在 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
    

    请记住,这些 __ne__ 使用 __eq__ 实现 - 虽然这是预期的行为,但实现不正确:

    assert not wrong1 == wrong2         # These are contradicted by the
    assert not wrong2 == wrong1         # below unexpected behavior!
    

    意外行为:

    请注意,此比较与上述比较相矛盾 (not wrong1 == wrong2)。

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

    和,

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

    在 Python 2 中不要跳过 __ne__

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

    >>> right_py3_1, right_py3_1child = BaseEquatable(1), ChildComparablePy3(1)
    >>> right_py3_1 != right_py3_1child # as evaluated in Python 2!
    True
    

    上面的结果应该是False!

    Python 3 源代码

    __ne__ 的默认 CPython 实现位于 typeobject.c in object_richcompare

    case Py_NE:
        /* By default, __ne__() delegates to __eq__() and inverts the result,
           unless the latter returns NotImplemented. */
        if (Py_TYPE(self)->tp_richcompare == NULL) {
            res = Py_NotImplemented;
            Py_INCREF(res);
            break;
        }
        res = (*Py_TYPE(self)->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);
            }
        }
        break;
    

    但是默认的__ne__ 使用__eq__

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

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

    使用== 允许我们将低级逻辑保留在一个位置,并避免__ne__ 中寻址NotImplemented

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

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

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

    还有比较:

    >>> f == f
    True
    >>> f != f
    False
    >>> f2 == f
    False
    >>> f2 != f
    True
    

    性能

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

    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
    

    我认为这些性能数据不言自明:

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

    当您考虑到 low_level_python 正在 Python 中执行本应在 C 级别处理的逻辑时,这是有道理的。

    回应一些批评

    另一位回答者写道:

    Aaron Hall 的 __ne__ 方法的实现 not self == other 是不正确的,因为它永远无法返回 NotImplementednot NotImplementedFalse),因此具有优先级的 __ne__ 方法永远不会退回到__ne__ 没有优先级的方法。

    拥有__ne__ 永远不会返回NotImplemented 并不意味着它不正确。相反,我们使用NotImplemented 通过检查与== 的相等性来处理优先级。假设== 正确实现,我们就完成了。

    not self == other 曾经是 __ne__ 方法的默认 Python 3 实现,但它是一个错误,并于 2015 年 1 月在 Python 3.4 中得到纠正,正如 ShadowRanger 所注意到的(参见问题 #21408)。

    好吧,让我们解释一下。

    如前所述,Python 3 默认处理__ne__,首先检查self.__eq__(other) 是否返回NotImplemented(单例) - 应使用is 检查并返回,否则返回逆。这是编写为类 mixin 的逻辑:

    class CStyle__ne__:
        """Mixin that provides __ne__ functionality equivalent to 
        the builtin functionality
        """
        def __ne__(self, other):
            equal = self.__eq__(other)
            if equal is NotImplemented:
                return NotImplemented
            return not equal
    

    这是 C 级 Python API 正确性所必需的,它是在 Python 3 中引入的,使得

    冗余。所有相关的__ne__ 方法都被删除了,包括实现自己的检查的方法以及直接或通过== 委托给__eq__ 的方法——而== 是最常用的方法。

    对称重要吗?

    我们坚持不懈的批评者提供了一个病态的例子来说明在__ne__ 中处理NotImplemented 的理由,重视对称性高于一切。让我们用一个明确的例子来论证这个论点:

    class B:
        """
        this class has no __eq__ implementation, but asserts 
        any instance is not equal to any other object
        """
        def __ne__(self, other):
            return True
    
    class A:
        "This class asserts instances are equivalent to all other objects"
        def __eq__(self, other):
            return True
    
    >>> A() == B(), B() == A(), A() != B(), B() != A()
    (True, True, False, True)
    

    所以,按照这个逻辑,为了保持对称性,我们需要编写复杂的__ne__,无论Python版本如何。

    class B:
        def __ne__(self, other):
            return True
    
    class A:
        def __eq__(self, other):
            return True
        def __ne__(self, other):
            result = other.__eq__(self)
            if result is NotImplemented:
                return NotImplemented
            return not result
    
    >>> A() == B(), B() == A(), A() != B(), B() != A()
    (True, True, True, True)
    

    显然我们不应该介意这些实例既相等又不相等。

    我认为对称性不如合理代码的假设和遵循文档的建议重要。

    但是,如果 A 有一个合理的 __eq__ 实现,那么我们仍然可以在这里按照我的方向进行操作,我们仍然会有对称性:

    class B:
        def __ne__(self, other):
            return True
    
    class A:
        def __eq__(self, other):
            return False         # <- this boolean changed... 
    
    >>> A() == B(), B() == A(), A() != B(), B() != A()
    (False, False, True, True)
    

    结论

    对于 Python 2 兼容代码,使用 == 来实现 __ne__。更多:

    • 正确
    • 简单
    • 高性能

    仅在 Python 3 中,使用 C 级别的低级否定 - 它甚至简单且高效(尽管程序员有责任确定它正确 em>)。

    再次,不要在高级 Python 中编写低级逻辑。

    【讨论】:

    • 优秀的例子!部分惊喜在于操作数的顺序根本不重要,这与某些具有“右侧”反射的魔术方法不同。重新迭代我错过的部分(并且花费了我很多时间):首先尝试 subclass 的丰富比较方法,无论代码是否具有超类或子类运算符的左侧。这就是为什么你的a1 != c2 返回False --- 它没有运行a1.__ne__,而是c2.__ne__,它否定了mixin 的 __eq__ 方法。因为NotImplemented 是真实的,所以not NotImplementedFalse
    • 您最近的更新确实成功地展示了 not (self == other) 的性能优势,但没有人认为它不快(嗯,无论如何都比 Py2 上的任何其他选项都快)。问题是在某些情况下它是错误的; Python本身曾经做not (self == other),但是因为it was incorrect in the presence of arbitrary subclasses而改变。最快到错误答案仍然是错误
    • 具体的例子确实有点不重要。问题是,在您的实现中,__ne__ 的行为委托给__eq__(如有必要,双方都可以),但它从不回退到另一方的__ne__即使__eq__ 都“放弃”。正确的__ne__ 委托给它的自己的 __eq__,但如果返回NotImplemented,它会退回到对方的__ne__,而不是反转对方的__eq__ (因为对方可能没有明确选择委派给__eq__,你不应该为此做出决定)。
    • @AaronHall:在今天重新审视这一点时,我不认为您的实现对于子类通常是有问题的(使其中断会非常复杂,而子类,假设对父母有充分的了解,应该能够避免它)。但我只是在我的回答中给出了一个不复杂的例子。非病态的情况是 SQLAlchemy 的 ORM,其中 __eq____ne__ 都没有返回 TrueFalse,而是一个代理对象(恰好是“真实的”)。错误地实现__ne__ 意味着订单对比较很重要(您只能在一个订单中获得代理)。
    • 明确地说,在 99%(或者可能是 99.999%)的情况下,您的解决方案很好,而且(显然)更快。但是由于您无法控制它不好的情况,作为一个库编写者,其代码可能被其他人使用(阅读:除了简单的一次性脚本和模块之外的任何东西个人使用),您必须使用正确的实现来遵守运算符重载的一般合同,并使用您可能遇到的任何其他代码。幸运的是,在 Py3 上,这些都不重要,因为您可以完全省略 __ne__。一年后,Py2 将死去,我们忽略这一点。 :-)
    【解决方案2】:

    是的,这很好。实际上,the documentation 敦促您在定义 __eq__ 时定义 __ne__

    没有隐含的关系 在比较运算符之间。这 x==y 的真实性并不意味着 x!=y 是假的。因此,当定义 __eq__(),还应该定义 __ne__() 以便操作符按预期运行。

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

    【讨论】:

    • this 是正确的答案(在这里,@aaron-hall)。您引用的文档鼓励您使用__eq__ 实现__ne__,只是您实现它。
    • @guyarad:实际上,由于没有正确授权,Aaron 的回答仍然有些错误; not self == other 不是将来自一侧的NotImplemented 返回作为委托给另一侧的__ne__ 的提示,而是(假设操作数的__eq__ 不知道如何比较另一个操作数)隐式委托给__eq__ 从另一边,然后反转它。对于奇怪的类型,例如SQLAlchemy ORM 的字段,这个causes problems
    • ShadowRanger 的批评仅适用于非常病态的病例(恕我直言),并在我下面的回答中得到充分解决。
    • 较新的文档(至少对于 3.7,可能更早)__ne__ 自动委托给 __eq__,并且文档中不再存在此答案中的引用。归根结底,只实现__eq__ 并让__ne__ 委托是完全pythonic。
    【解决方案3】:

    仅作记录,规范正确且跨 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
    

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

    • not (self == other) 不同,不会干扰涉及比较的一些烦人/复杂情况,其中涉及的类之一并不意味着__ne__ 的结果与not 在@ 上的结果相同987654329@(例如 SQLAlchemy 的 ORM,其中 __eq____ne__ 返回特殊的代理对象,而不是 TrueFalse,并尝试 not __eq__ 的结果将返回 False,而不是正确的代理对象)。
    • not self.__eq__(other) 不同,当self.__eq__ 返回NotImplemented 时,它正确地委托给另一个实例的__ne__not self.__eq__(other) 会额外错误,因为NotImplemented 是真实的,所以当__eq__ 没有'不知道如何进行比较,__ne__ 将返回False,暗示这两个对象相等,而实际上唯一被问到的对象不知道,这意味着默认不相等)

    如果您的__eq__ 不使用NotImplemented 返回,则此方法有效(具有无意义的开销),如果它有时确实使用NotImplemented,则可以正确处理它。并且 Python 版本检查意味着如果类在 Python 3 中为 import-ed,则 __ne__ 未定义,允许 Python 的原生、高效的后备 __ne__ implementation (a C version of the above) 接管。


    为什么需要这个

    Python 重载规则

    为什么你这样做而不是其他解决方案的解释有点神秘。 Python 有一些关于重载运算符的一般规则,尤其是比较运算符:

    1. (适用于所有运算符)运行LHS OP RHS 时,尝试LHS.__op__(RHS),如果返回NotImplemented,请尝试RHS.__rop__(LHS)。例外:如果RHSLHS 类的子类,则测试RHS.__rop__(LHS) 首先。在比较运算符的情况下,__eq____ne__ 是它们自己的“rop”(因此__ne__ 的测试顺序是LHS.__ne__(RHS),然后是RHS.__ne__(LHS),如果RHS 是@ 的子类,则相反987654365@的班级)
    2. 除了“交换”运算符的概念之外,运算符之间没有隐含的关系。即使是同一类的实例,LHS.__eq__(RHS) 返回 True 并不意味着 LHS.__ne__(RHS) 返回 False(实际上,运算符甚至不需要返回布尔值;像 SQLAlchemy 这样的 ORM 故意不这样做,允许更具表现力的查询语法)。从 Python 3 开始,默认的 __ne__ 实现以这种方式运行,但它不是契约性的;您可以以与__eq__ 不完全相反的方式覆盖__ne__

    这如何应用于重载比较器

    所以当你重载一个操作符时,你有两个工作:

    1. 如果您自己知道如何实现操作,请使用您自己对如何进行比较的了解(切勿隐式或显式委托给操作的另一方;这样做有不正确和/或无限递归的风险,具体取决于您的操作方式)
    2. 如果您知道如何自己实现该操作,总是返回NotImplemented,以便 Python 可以委托给其他操作数的实现

    not self.__eq__(other) 的问题

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

    从不委托给另一方(如果__eq__ 正确返回NotImplemented 则不正确)。当self.__eq__(other) 返回NotImplemented(这是“真实的”)时,你默默地返回False,所以A() != something_A_knows_nothing_about 返回False,它应该检查something_A_knows_nothing_about 是否知道如何与@987654384 的实例进行比较@,如果没有,它应该返回True(因为如果双方都不知道如何与对方进行比较,则认为它们不等于彼此)。如果 A.__eq__ 实现不正确(返回 False 而不是 NotImplemented 当它不识别另一边时),那么从 A 的角度来看这是“正确的”,返回 True(因为 @987654391 @ 认为它不相等,所以它不相等),但从 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
    

    更微妙。这对于 99% 的类都是正确的,包括所有 __ne____eq__ 逻辑逆的类。但是not self == other 打破了上面提到的两条规则,这意味着对于__ne__ 不是__eq__ 的逻辑相反的类,结果再次是非对称的,因为其中之一永远不会询问操作数是否可以实现__ne__,即使另一个操作数不能。最简单的例子是一个怪异类,它返回False 用于所有 比较,所以A() == Incomparable()A() != Incomparable() 都返回False。通过正确实现A.__ne__(当它不知道如何进行比较时返回NotImplemented),关系是对称的; A() != Incomparable()Incomparable() != A() 同意结果(因为在前一种情况下,A.__ne__ 返回NotImplemented,然后Incomparable.__ne__ 返回False,而在后者中,Incomparable.__ne__ 直接返回False)。但是当A.__ne__被实现为return not self == other时,A() != Incomparable()返回True(因为A.__eq__返回,而不是NotImplemented,那么Incomparable.__eq__返回False,而A.__ne__987654428@9872@ ),而Incomparable() != A() 返回False.

    您可以在here 看到一个这样的例子。

    显然,对于__eq____ne__ 总是返回False 的类有点奇怪。但如前所述,__eq____ne__ 甚至不需要返回True/False; SQLAlchemy ORM 具有带有比较器的类,这些比较器返回一个用于查询构建的特殊代理对象,而不是 True/False(如果在布尔上下文中评估它们是“真实的”,但它们永远不应该被评估在这种情况下)。

    如果未能正确重载 __ne__,您破坏此类类,如代码所示:

     results = session.query(MyTable).filter(MyTable.fieldname != MyClassWithBadNE())
    

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

     results = session.query(MyTable).filter(MyClassWithBadNE() != MyTable.fieldname)
    

    最终将传递filter 一个普通的False,因为self == other 返回一个代理对象,而not self == other 只是将真实的代理对象转换为False。希望filter 在处理像False 这样的无效参数时抛出异常。虽然我相信很多人会争辩说MyTable.fieldname 应该始终位于比较的左侧,但事实仍然是在一般情况下没有程序化的理由来强制执行此操作,并且正确的通用 __ne__ 可以以任一方式工作,而 return not self == other 只能以一种方式工作。

    【讨论】:

    • 唯一正确、完整和诚实(对不起@AaronHall)的答案。这应该是公认的答案。
    • 您可能会对我更新的答案感兴趣,该答案使用我认为比您的 Incomparable 类更强的论据,因为该类打破了 != 和 @ 之间的 complement 关系987654459@ 运算符,因此可能被视为无效或“病态”示例,如@AaronHall 所说。我承认@AaronHall 指出您的 SQLAlchemy 参数可能被认为是无关紧要的,因为它是在非布尔上下文中。 (您的论点仍然非常有趣且经过深思熟虑。)
    • +1。对于很久以前忘记为 Python 2 实现 __ne__ 的现有项目,我只是在寻找最能模仿 Python 3-without-__ne__ 行为的 __ne__ shim,以防止现有 Python 的回归3 用户,即使在病态的情况下。我针对其他几个类测试了@AaronHall 的解决方案,其中一些无可否认地令人费解,但它有时不会返回与 Python 3-without-__ne__ 相同的结果。相比之下,这个 @ShadowRanger/@Maggyero 解决方案的行为总是与 Python 3-without-__ne__ 完全相同,无论我向它扔什么疯狂的东西。
    【解决方案4】:

    __ne__的正确实现

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

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

    也恰好是__ne__since Python 3.4这个特殊方法的默认实现,如Python documentation中所述:

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

    另请注意,为不受支持的操作数返回值 NotImplemented 并不特定于特殊方法 __ne__。事实上,所有特殊的比较方法1和特殊的数值方法2对于不支持的操作数应该返回值NotImplemented,如Python documentation:

    未实现

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

    Python documentation 中给出了特殊数值方法的示例:

    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
    

    1 特殊比较方法:__lt____le____eq____ne____gt____ge__

    2 特殊数值方法:__add____sub____mul____matmul____truediv____floordiv____mod____divmod__、@987654373 @、__lshift____rshift____and____xor____or__ 和它们的 __r*__ 反射和 __i*__ 就地对应物。

    __ne__ #1 的错误实现

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

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

    这个实现的问题在于它不会依赖另一个操作数的特殊方法__ne__,因为它永远不会返回值NotImplemented(表达式not self.__eq__(other)的计算结果是True或@ 987654388@,包括它的子表达式 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
    

    __ne__ #2 的错误实现

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

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

    这个实现的问题在于它直接依赖于另一个操作数的特殊方法__eq__,绕过了另一个操作数的特殊方法__ne__,因为它从不返回值NotImplemented(表达式@ 987654403@ 依赖于另一个操作数的特殊方法__eq__ 并计算为值TrueFalse)。绕过一个方法是不正确的,因为该方法可能有side effects,比如更新对象的状态:

    class Correct:
    
        def __init__(self):
            self.state = False
    
        def __ne__(self, other):
            self.state = True
            result = self.__eq__(other)
            if result is not NotImplemented:
                return not result
            return NotImplemented
    
    
    class Incorrect:
    
        def __init__(self):
            self.state = False
    
        def __ne__(self, other):
            self.state = True
            return not self == other
    
    
    x, y = Correct(), Correct()
    assert x != y
    assert x.state == y.state
    
    x, y = Incorrect(), Incorrect()
    assert x != y
    assert x.state == y.state  # AssertionError
    

    了解比较操作

    在数学中,集合 X 上的 binary relation R 是一组有序对 (x, y) 在 X2 中。 R 中的语句 (x,y) 读作 'x is R-与y'相关,用xRy表示。

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

    • Rreflexive 当所有 xX, xRx
    • Rirreflexive(也称为 strict),对于 X 中的所有 x,而不是 xRx
    • X 中的所有 xy 时,Rsymmetric,如果 xRy 然后 yRx.
    • X 中的所有 xy 时,Rantisymmetric,如果 xRyyRx 然后 x = y
    • X 中的所有 xyz 时,Rtransitive , 如果 xRyyRzxRz.
    • 当对于 X、xRyyRx
    • R 是自反、对称和传递时,Requivalence relation
      例如,=。然而 ≠ 只是对称的。
    • R 是自反、反对称和传递时,Rorder relation
      例如,≤ 和 ≥。
    • Rstrict order relation,当 R 是反自反的、反对称的和传递的。
      例如,。然而 ≠ 只是不自反的。

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

    • Rconverse 是二元关系 RT = {(yx) | xRy} 超过 X
    • Rcomplement 是二元关系 ¬R = {(xy) |不是 xRy} 而不是 X
    • RSunion是二元关系R ∪ S = {(x,y)| xRyxSy} 超过 X

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

    • 2个互补关系:=和≠互为互补;
    • 6个逆向关系:=是自身逆向关系,≠是自身逆向关系,是彼此逆向关系,≤和≥是彼此逆向关系;
    • 2 个并集关系:≤ 是 和 = 的并集。

    仅对connex顺序关系有效的比较关系之间的关系:

    • 4 种互补关系: 和 ≤ 互为补码。

    所以要在Python中正确实现比较运算符==!=&lt;&gt;&lt;=&gt;=对应比较关系=、≠、、≤ , 和 ≥, 上述所有数学性质和关系都应该成立。

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

    class X:
    
        def __operator__(self, other):
            # implementation
    

    因为 Rreflexive 意味着 xRx,所以一个自反比较操作 x operator y (x == y, x &lt;= yx &gt;= y ) 或自反特殊比较方法调用 x.__operator__(y)x.__eq__(y)x.__le__(y)x.__ge__(y))如果 xy 相同,则应计算为值 True,即如果表达式 x is y计算结果为 True。由于 Rirreflexive 意味着不是 xRx,因此一个非自反比较操作 x operator yx != yx &lt; yx &gt; y)或非自反特殊比较方法调用 x.__operator__(y)x.__ne__(y)x.__lt__(y)x.__gt__(y))如果 xy 相同,则应计算为值 False,即如果表达式 x is y 计算为True。对于比较运算符== 和相关的特殊比较方法__eq__,Python 考虑了自反属性,但对于比较运算符&lt;=&gt;= 以及相关的特殊比较方法__ge__,考虑了surprisingly not considered,对于比较运算符!= 和相关的特殊比较方法__ne__,Python 考虑了非自反属性,但对于比较运算符&lt;&gt; 以及相关的特殊比较方法__lt____gt__,考虑了surprisingly not considered .被忽略的比较运算符会引发异常TypeError(并且相关的特殊比较方法会返回值NotImplemented),如Python documentation 中所述:

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

    未提供默认订单比较(&lt;&gt;&lt;=&gt;=); 尝试引发TypeError。这种默认行为的动机 是缺少与相等性类似的不变量。 [这是不正确的,因为 &lt;=&gt;=== 一样是自反的,而 &lt;&gt;!= 一样是反身的。]

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

    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&lt;y 调用 x.__lt__(y), x&lt;=y 呼叫x.__le__(y), x==y 呼叫x.__eq__(y), x!=y 致电 x.__ne__(y)x&gt;y 致电 x.__gt__(y)x&gt;=y 致电x.__ge__(y)

    富比较方法可能返回单例NotImplemented if 它不为给定的一对参数实现操作。

    […]

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

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

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

    在CPython中this is implemented in C code,可以翻译成Python代码(名称eq代表==ne代表!=lt代表&lt;gt代表@987654523 @, le&lt;=ge&gt;=):

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

    由于 R = ¬(¬R),比较 xRy 等价于 比较 ¬ (x¬Ry)。 ≠ 是 = 的补码,所以特殊方法 __ne__ 是按照特殊方法 __eq__ 支持的操作数默认实现的,而其他特殊比较方法默认独立实现(事实上 ≤ 是并集 和 = 的并集是 surprisingly not considered,这意味着当前特殊方法 __le____ge__ 应该是用户实现的),如 Python documentation 中所述:

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

    在CPython中this is implemented in C code,可以翻译成Python代码:

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

    所以默认情况下:

    • 比较操作x operator y 引发异常TypeError 除了比较操作符==!= 如果操作数x 和@987654556 分别返回值TrueFalse @分别相同和不相同,否则为FalseTrue
    • 一个特殊的比较方法调用x.__operator__(y)返回值NotImplemented,除了特殊的比较方法__eq____ne__,它分别返回值TrueFalse,如果操作数xy分别相同和不相同,否则为NotImplemented

    【讨论】:

    • 到你的最后一个例子:“由于当__eq__ 方法返回 NotImplemented 时,此实现无法复制__ne__ 方法的默认实现的行为,所以这是不正确的。” - A 定义无条件相等。因此,A() == B()。因此A() != B()应该是False,而它。给出的示例是病态的(即__ne__ 不应该返回字符串,__eq__ 不应该依赖于__ne__ - 而__ne__ 应该依赖于__eq__,这是 Python 3 中的默认期望)。在你改变主意之前,我对这个答案仍然是 -1。
    • 最后一个示例有两个类,B,在所有检查 __ne__ 时返回一个真实字符串,A 在所有检查 __eq__ 时返回 True。这是一个病态的矛盾。在这样的矛盾下,最好提出一个例外。在不知道B 的情况下,A 没有义务尊重B 出于对称目的对__ne__ 的实现。在这个例子中,A 如何实现__ne__ 与我无关。请找到一个实际的、非病态的案例来说明你的观点。我已经更新了我的回答来解决你的问题。
    • SQLAlchemy 的用例是针对特定领域的语言。如果您正在设计这样的 DSL,那么您可能会将这里的所有建议都抛在脑后。为了继续折磨这个糟糕的类比,您的示例期望飞机有一半时间向后飞行,而我的示例只期望它们向前飞行,我认为这是一个合理的设计决策。我认为你提出的担忧是没有根据的,而且是倒退的。
    【解决方案5】:

    如果所有__eq____ne____lt____ge____le____gt__ 都对类有意义,那么只需实现__cmp__。否则,照你的做,因为 Daniel DiPaolo 说过的那句话(当我测试它而不是查找它时;))

    【讨论】:

    • Python 3.x 不再支持__cmp__() 特殊方法,所以你应该习惯使用丰富的比较运算符。
    • 或者,如果您使用的是 Python 2.7 或 3.x,则 functools.total_ordering 装饰器也非常方便。
    • 感谢您的提醒。不过,在过去的一年半里,我开始意识到很多事情。 ;)
    猜你喜欢
    • 1970-01-01
    • 2014-08-18
    • 2015-03-17
    • 2017-08-18
    • 1970-01-01
    • 2016-04-12
    • 1970-01-01
    • 2011-06-07
    • 1970-01-01
    相关资源
    最近更新 更多