Python 从dict继承时对象比较行为不一致

Python 从dict继承时对象比较行为不一致,python,comparison-operators,Python,Comparison Operators,这个问题源于一个失败的测试,该测试拒绝在本地失败,并且只会在我们的CI服务器上失败 结果证明,一些相当狡猾的对象比较是无意中进行的 我现在很好奇为什么相同Python版本(2.7.9)的两个安装之间的行为如此不同 这个测试用例可能会进一步简化,但我得到的是: import operator class Thing(dict): def __int__(self, number): return self['number'] def __gt__(self,

这个问题源于一个失败的测试,该测试拒绝在本地失败,并且只会在我们的CI服务器上失败

结果证明,一些相当狡猾的对象比较是无意中进行的

我现在很好奇为什么相同Python版本(2.7.9)的两个安装之间的行为如此不同

这个测试用例可能会进一步简化,但我得到的是:

import operator


class Thing(dict):
    def __int__(self, number):
        return self['number']

    def __gt__(self, other):
        return self['number'] > other

thing = Thing({'number': 2})

for o in [
        operator.lt,
        operator.le,
        operator.eq,
        operator.ne,
        operator.ge,
        operator.gt]:
    print o
    print o(0.01, thing)
    print o(thing, 0.01)
本地运行的结果是:

<built-in function lt>
True
False
<built-in function le>
True
False
<built-in function eq>
False
False
<built-in function ne>
True
True
<built-in function ge>
False
True
<built-in function gt>
False
True

真的
假的
真的
假的
假的
假的
真的
真的
假的
真的
假的
真的
但在Travis CI服务器上,它是:

<built-in function lt>
True
True
<built-in function le>
False
True
<built-in function eq>
False
False
<built-in function ne>
True
True
<built-in function ge>
True
False
<built-in function gt>
True
True

真的
真的
假的
真的
假的
假的
真的
真的
真的
假的
真的
真的
Python又回到了什么样的比较行为,为什么它会在同一版本的两个安装上表现出如此不同的行为

我最初的想法是某种基于
id
的比较,但从
id
的值来看,它们与比较的结果根本不相关

更新: 只有当类继承自
dict
时,才会发生这种不同的行为。当它从
对象
继承时,两个安装上的比较行为相同,并给出与上面的本地结果相同的结果

更新2:
我刚刚发现,我可以使用
\uu int\uuuu
\uu gt\uuu
方法进一步简化测试用例,但是如果我删除其中任何一个方法,那么奇怪的行为就会消失。

如注释中所述,
dict
已经定义了所有比较运算符。这种行为是:

不同类型的对象,除了不同的数字类型和不同的字符串类型外,从不进行相等比较;这样的对象是一致但任意排列的

换句话说,DICT的具体定义是允许与其他类型进行比较,但这种比较的结果是未定义的。(在Python3中对此进行了更改,因此不再允许进行此类类型间比较。)

当您为您的类型重写一些比较运算符时,会使事情更加复杂。由于您的类型定义了
\uu gt\uu
而不是
\uu lt\uu
thing>0.01
将使用您的自定义
\ugt\uu
,但是
thing<0.01
将使用默认(未定义)比较行为。因此,您得到的类型有时使用确定性规则,有时给出未定义的行为,具体取决于您使用的比较运算符。我不知道你为什么会看到你看到的结果的精确模式,但底线是你的类依赖于未定义的行为,所以你不能期望使用这种类型的比较有任何一致性。Python的两个实现可能在某种神秘的实现级别上做了不同的事情,从而产生不同的未定义行为。未定义行为的要点是你不应该知道它是如何工作的(或者你可能开始依赖它)


顺便说一句,
total_ordering
这里是一个no op,如果删除它,行为应该是相同的
total_ordering
只添加了尚未定义的比较运算符,但
dict
已经定义了所有这些运算符,因此
total_ordering
不会做任何事情。如果您想在已经定义了自己的比较行为(如dict)的类型的子类上建立自己的排序关系,那么您需要手动覆盖每个单独的比较运算符。

经过进一步的调查,根据@BrenBarn的精彩回答,我找到了奇怪行为的根源


“未定义”比较的最后一步是比较对象类型的内存位置。在比较本地和CI服务器上的
id(type(thing))
id(type(0.02))
之后,我发现
thing
的id在本地总是较高,在CI服务器上总是较低

一个有趣的问题!如果您使用的类只是继承自
对象而不是
dict
,那么您是否可以重现该行为?我还没有弄清楚到底发生了什么,但需要注意的是,
dict
定义了一些你的类没有定义的比较操作(实际上它定义了所有普通的不等式比较)。在我的机器上尝试这一点,le、ne和ge不调用类中定义的函数。这对我来说似乎很有趣,因为您的服务器和本地计算机上的答案不同,但据我所知,如果只使用内置程序,则不同的答案是。。非常奇怪?我刚才也在想同样的事情,发现这种行为只有从
dict
继承时才会发生。我已经简化了上面的测试用例。从
对象继承时,它们在两个安装上的行为相同。
functools.total\u ordering
-->“填充缺少的排序方法的类装饰器”。。。但是,
dict
@tdelaney中没有缺少排序方法,这是一个很好的观点!但是,有什么可以解释这两个安装之间的行为差异呢?我很难理解的一点行为是,当我从类中删除
\uuuu int\uuu
方法时,它不再显示这种行为,但是在进行比较时,
\uu int\uuu
方法永远不会被调用..啊,如果对象具有
\uuu int\uu
方法,则
PyNumber\u检查是否返回
True
?如果它们都通过了
PyNumber\u检查
,那么它就会回到最后的比较,即内存指针?@Acorn:听起来似乎有道理。我同意这是令人惊讶的,但它仍然是有效的未定义行为:-)。是的,在进一步调查之后,最后的比较方法是比较对象类型的内存位置。在本地和CI服务器上比较
id(type(thing))
id(type(0.02))
之后