是否应该将“不”作为“等式”的否定来实现?

我有一个类,我想重写\uuuuueq\uuu方法。我也应该重写\uu\ne\uu方法,这似乎是有道理的。我应该将\uu ne\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

A类:
定义初始化(自我,状态):
self.state=状态
定义(自身、其他):
返回self.state==other.state
定义(自身、其他):
返回非自我。均衡(其他)

Python,我应该基于\uuu eq\uu实现\uu ne\uuu()操作符吗

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

在Python3中,=是默认情况下对==的否定,因此您甚至不需要编写\uuu ne\uuuu,文档也不再坚持编写一个

一般来说,对于Python3-only代码,除非需要掩盖父实现(例如,对于内置对象),否则不要编写代码

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

只有在以下情况下,\uuu ne\uuuuuuu方法才会从\uuuu eq\uuuu自动执行
超类中尚未定义\uuu ne\uu。所以,如果你是
从内置继承,最好同时覆盖这两个

如果需要代码在Python2中工作,请遵循Python2的建议,它在Python3中也可以正常工作

在Python2中,Python本身不会自动实现任何与另一个操作相关的操作-因此,您应该根据==而不是\uuuuuu eq.来定义\uu ene.
例如

A类(对象):
定义(自身、其他):
返回self.value==other.value
定义(自身、其他):
return not self==其他#not `返回not self.uuu eq_uu(其他)`

看到证据了吗

  • 基于\uuu eq\uu
  • 根本没有在Python2中实现\uuu ne\uu

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

长话短说

Python 2的文档说明:

比较运算符之间没有隐含的关系。这个
x==y的真理并不意味着x=y为假。因此,当
定义\uuuu eq\uuuu(),还应定义\uu ne\uuuuu(),以便
操作员将按预期操作

这意味着如果我们用\uuuu eq\uuu的倒数来定义\uu ne\uuuu,我们可以得到一致的行为

本节文档已针对Python 3进行了更新:

默认情况下,\uuu ne\uuu()委托给\uu eq\uu(),并反转结果
除非未实施

在“最新消息”部分,我们看到这种行为发生了变化:

  • =现在返回与==相反的值,除非==返回未实现

为了实现\uuu ne\uuuu,我们更喜欢使用=操作符而不是直接使用\uuuuuueq\uuuuu方法,这样如果子类的self.\uuuuueq\uuuuuuuuu(其他)返回未实现的,对于选中的类型,Python将适当地检查其他。\uuueq\uuuuuuuuuuuuuuuuuuuuuself来自文档:

NotImplemented对象

此类型只有一个值。只有一个对象具有此值。通过内置名称访问此对象
未实施。数值方法和丰富的比较方法可能返回
如果不执行操作数的操作,则返回此值
假如(然后解释器将尝试反射操作,或
其他一些回退,取决于运算符。)其真值为

当给定一个丰富的比较运算符时,如果它们不是相同的类型,Python将检查其他是否是一个子类型,如果定义了该运算符,则首先使用其他的方法(与<lt;<lt;<gt;相反)。如果返回NotImplemented,则将使用相反的方法。(它不会两次检查相同的方法。)使用=运算符允许此逻辑发生


期望值

从语义上讲,您应该在检查平等性方面实现\uu______,因为您的类的用户希望以下函数对于一个类的所有实例都是等效的:

def否定(inst1,inst2):
“”“始终应返回与not_equals相同的值(inst1,inst2)”
返回not inst1==inst2
def不等于(inst1,inst2):
“”“始终应返回与_equals(inst1,inst2)的否定_相同的结果”
返回inst1!=inst2

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

基于\uuuuu eq\uuuu定义\uuuu ne\uuuuuuuu>时的意外行为演示:

首先,设置:

类baseequalable(对象):
定义初始化(self,x):
self.x=x
定义(自身、其他):
返回isinstance(其他,BaseEqualable)和self.x==other.x
类别可比性错误(基本可比):
定义(自身、其他):
返回非自我。均衡(其他)
类别可比权利(基本可比):
定义(自身、其他):
返回非self==其他
类EqMixin(对象):
定义(自身、其他):
“”“覆盖基本均衡和反弹到其他均衡,例如。
如果issubclass(类型(self),类型(other)):#在本例中为True
"""
返回未执行
类childComparableError(EqMixin,ComparableError):
“\uuuu ne\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
类别儿童可比权利(EqMixin,可比权利):
“”“使用正确的方法(使用==)”
类childCompariablePy3(EqMixin,baseequalable):
“”“否,仅在Python 3中正确。”“”

实例化非等效实例:

right1,right2=ComparableRight(1),ChildComparableRight(2)
错误1,错误2=可比错误(1),儿童可比错误(2)
right_py3_1,right_py3_2=baseequalable(1),childCompariablePy3(2)

预期行为:

(注意:虽然以下每一项的每一秒断言都是等价的,因此在逻辑上与前面的断言是多余的,但我将它们包括进来,以证明当一项是另一项的子类时,顺序并不重要。

这些实例使用=实现了

断言不正确1==right2
断言非right2==right1
断言权利1!=对2
断言权利2!=对1

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

断言不正确\u py3\u 1==right\u py3\u 2
断言不正确\u py3\u 2==正确\u py3\u 1
主张权利_py3_1!=右py3 2
主张权利_py3_2!=右_py3_1

回想一下,这些都是用\uuuuuuu-ne\uuuuuuuuuuuu实现的\uuuuuuuu-eq\uuuuuuuuu——虽然这是预期的行为,但实现是不正确的:

断言没有错误1==error 2#这些与
断言不错误2==错误1#低于意外行为!

意外行为:

请注意,此比较与上面的比较相矛盾(非错误1==错误2

&gt&燃气轮机&燃气轮机;断言错误1!=错误2
回溯(最近一次呼叫最后一次):
文件“&lt;stdin&gt;”,第1行,在&lt;模块&gt;
断言错误

以及

&gt&燃气轮机&燃气轮机;断言错误2!=错误1
回溯(最近一次呼叫最后一次):
文件“&lt;stdin&gt;”,第1行,在&lt;模块&gt;
断言错误

在Python2中不要跳过\u___

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

&gt&燃气轮机&燃气轮机;right_py3_1,right_py3_1儿童=基本可平等(1),儿童可比较py3(1)
&燃气轮机&燃气轮机&燃气轮机;右_py3_1!=右_py3_1child#在Python 2中进行了评估!
符合事实的

上述结果应为

Python3源代码

的默认CPython实现在对象的类型对象.c中:

案例:
/*默认情况下,_ne__()委托给_eq__()并反转结果,
除非后者返回未实现*/
如果(Py_类型(自)-&gt;tp_richcompare==NULL){
res=未执行的Py_;
Py_增量(res);
打破
}
res=(*Py_类型(self)-&gt;tp_-richcompare)(self、other、Py_-EQ);
if(res!=NULL&amp;res!=Py_未实现){
int ok=PyObject_IsTrue(res);
Py_DECREF(res);
如果(正常&lt;0)
res=NULL;
否则{
如果(确定)
res=Py_假;
其他的
res=Py_-True;
Py_增量(res);
}
}
打破

但是默认的\uuu ne\uuuuuuuuu使用\uuuuu eq\uuuuuu

Python3在C级的默认实现细节使用\uuuuuuueq\uuuuuu,因为更高级别的=(PyObject\uRichCompare)效率更低,因此它还必须处理未实现的

如果\uuuu eq\uuuu正确实现,那么=的否定也是正确的-它允许我们避免低级别实现

发表评论