繁体   English   中英

__ne__ 应该作为 __eq__ 的否定来实现吗?

[英]Should __ne__ be implemented as the negation of __eq__?

我有一个 class,我想在其中覆盖__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,我应该基于__eq__实现__ne__()运算符吗?

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

在 Python 3 中, !=默认情况下是==的否定,因此您甚至不需要编写__ne__ ,并且文档不再固执地写一个。

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

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

仅当__ne__尚未在超类中定义时, __ne__方法才会自动从__eq__跟随。 因此,如果您从内置函数继承,最好同时覆盖两者。

如果您需要您的代码在 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 的文档说:

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

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

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

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

“新增内容”部分,我们看到这种行为发生了变化:

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

为了实现__ne__ ,我们更喜欢使用==操作符而不是直接使用__eq__方法,这样如果子类的self.__eq__(other)为检查的类型返回NotImplemented ,Python 将适当地检查other.__eq__(self) 从文档

NotImplemented对象

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

当给定一个丰富的比较运算符时,如果它们不是相同的类型,Python 会检查other是否是子类型,如果它定义了那个运算符,它首先使用other的方法(对于<<=>=> )。 如果返回NotImplemented它使用相反的方法。 (它检查用于相同方法的两倍。)使用==运算符允许对这个逻辑发生。


期望

从语义__ne__ ,您应该在检查相等性方面实现__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

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

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 实现位于object_richcompare中的typeobject.c

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 的实现not self == other__ne__方法是不正确的,因为它永远不会返回NotImplementednot NotImplementedFalse ),因此具有优先级的__ne__方法永远不会退回到没有优先级的__ne__方法上。

__ne__从不返回NotImplemented并不会使它不正确。 相反,我们通过检查与==相等性来处理NotImplemented优先级。 假设==正确实现,我们就完成了。

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

好吧,让我们解释一下。

如前所述,Python 3 默认通过首先检查self.__eq__(other)返回NotImplemented (单例)来处理__ne__ - 应该使用is检查并返回,否则返回相反值。 这是作为类混合编写的逻辑:

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的情况,将对称性__ne__ 让我们用一个明显的例子来论证这个论点:

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 级别使用低级否定 - 它更加简单和高效(尽管程序员负责确定它是正确的)。

同样,在做高层次的Python写低层次的逻辑。

是的,这完全没问题。 实际上, 文档敦促您在定义__eq__时定义__ne__

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

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

只是为了记录,规范正确和跨 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 on __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 版本检查意味着,如果该类在 Python 3 中被import -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”(因此__ne__的测试顺序是LHS.__ne__(RHS) ,然后是RHS.__ne__(LHS) ,如果RHSLHS的子类,则相反班级)
  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 __eq__正确)。 self.__eq__(other)返回NotImplemented (这是“真实的”)时,你默默地返回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_about A() != 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 == otherA() != Incomparable()返回True (因为A.__eq__返回,而不是NotImplemented ,然后Incomparable.__eq__返回False ,而A.__ne__反转为True ),而Incomparable() != A()返回False.

您可以在此处查看此操作的示例。

显然,一个总是为__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只适用于一种安排。

正确的__ne__实现

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

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

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

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

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

未实现

这种类型只有一个值。 有一个具有此值的对象。 该对象通过内置名称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

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)

此实现的问题在于它不会回__ne__另一个操作数的特殊方法__ne__ ,因为它从不返回值NotImplemented (表达式not self.__eq__(other)计算结果为TrueFalse ,包括其子表达式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 (表达式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

了解比较操作

在数学中,二元关系在一组X,R是一组中的X 2有序对(X,Y)的。 R中的声明(X,Y)读取“XR -相关至y”,并通过XRY表示。

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

  • R自反X,XRX所有的x时。
  • R漫反射的(也称为严格)当在X所有的x,而不是XRX。
  • 当对于X 中的所有xy时, R对称的,如果xRyyRx
  • 当对于X 中的所有xy时, R反对称的,如果xRyyRxx = y
  • R传递对于所有的x,yzX时,如果XRYyRz然后XRZ。
  • 当对于XxRyyRx 中的所有xy时, R连接(也称为总计)。
  • R是自反的、对称的和可传递的时, R是一个等价关系
    例如,=。 然而≠只是对称的。
  • R顺序关系,当R是自反的,反对称和传递。
    例如,≤ 和 ≥。
  • R严格的顺序关系R是漫反射的,反对称和传递。
    例如,< 和 >。 然而≠只是非自反的。

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

  • R是二元关系R T = {( y , x ) | xRy } 超过X
  • R补码是二元关系 ¬ R = {( x , y ) | 不是xRy } 超过X
  • 联合RS的是二进制关系R∪S = {(X,Y)| xRyxSy } 超过X

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

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

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

  • 4 互补关系:<和≥互为补,>和≤互为补。

因此,要在 Python 中正确实现比较运算符==!=<><=>=对应于比较关系 =、≠、<、>、≤ 和 ≥,所有上述数学属性和关系应该持有。

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

class X:

    def __operator__(self, other):
        # implementation

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

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

未提供默认顺序比较( <><=>= ); 尝试引发TypeError 这种默认行为的动机是缺乏与平等类似的不变量。 [这是不正确的,因为<=>===一样自反,而<>!=一样不自反。]

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<y调用x.__lt__(y)x<=y调用x.__le__(y)x==y调用x.__eq__(y)x!=y调用x.__ne__(y)x>y调用x.__gt__(y)x>=y调用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)y.__operatorT__(x) Python 使用以下计算策略:

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

在 CPython 中, 这是用 C 代码实现的,它可以翻译成 Python 代码(名称eq表示==ne表示!=lt表示<gt表示>le表示<=ge表示>= ):

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__实现,而其他特殊比较方法默认独立实现(≤ 是 < 和 = 的并集) , 和 ≥ 是 > 和 = 的并集, 令人惊讶的没有考虑,这意味着当前特殊方法__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
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除了比较运算符==!= ,如果操作数xy分别相同和不相同,则分别返回值TrueFalse ,以及值FalseTrue除此以外;
  • 一个特殊的比较方法调用x.__operator__(y)返回值NotImplemented除了特殊的比较方法__eq____ne__ ,如果操作数xy分别相同和不相同,它分别返回值TrueFalse ,并且value NotImplemented否则。

如果__eq____ne____lt____ge____le____gt__对类__gt__意义,那么只需实现__cmp__ 否则,照你所做的去做,因为 Daniel DiPaolo 说的那一点(当我正在测试它而不是查找它时;))

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM