从 dict 继承时不一致的对象比较行为
Inconsistent object comparison behaviour when inheriting from dict
这个问题是由拒绝在本地失败的失败测试引起的,并且只会在我们的 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
的值来看,它们与比较结果根本不相关。
更新:
只有当 class 继承自 dict
时才会出现这种不同的行为。当它继承自 object
时,两个安装的比较行为相同,并给出与上述本地结果相同的结果。
更新 2:
我刚刚发现我可以仅使用 __int__
和 __gt__
方法进一步简化测试用例,但是如果我删除其中任何一个方法,那么奇怪的行为就会消失。
如评论中所述,dict
已经定义了所有比较运算符。 documented 行为是:
Objects of different types, except different numeric types and different string types, never compare equal; such objects are ordered consistently but arbitrarily
换句话说,dicts被专门定义为允许与其他类型进行比较,但这种比较的结果是未定义的。 (这在 Python 3 中已更改,因此不再允许此类类型间比较。)
当您只为您的类型重写一些比较运算符时,事情会变得更加复杂。由于您的类型定义了 __gt__
但未定义 __lt__
,因此 thing > 0.01
将使用您的自定义 __gt__
,但 thing < 0.01
将使用默认(未定义)比较行为。因此,您得到的类型有时会使用确定性规则,有时会给出未定义的行为,具体取决于您使用的比较运算符。我不知道为什么你会看到你所看到的精确结果模式,但最重要的是你的 class 依赖于未定义的行为,所以你不能期望使用这种类型的比较有任何一致性。 Python 的两个实现可能在某些神秘的实现级别上做不同的事情,从而产生不同的未定义行为。未定义行为的要点是您不应该知道它是如何工作的(否则您可能会开始依赖它)。
顺便说一句,total_ordering
这里是一个空操作,如果你删除它,行为应该是一样的。 total_ordering
只添加尚未定义的比较运算符,但 dict
已经定义了所有这些,因此 total_ordering
不会做任何事情。如果您想在已经定义了自己的比较行为(如字典)的类型的子 class 上建立自己的排序关系,那么您需要手动覆盖每个单独的比较运算符。
经过进一步调查,根据@BrenBarn 的精彩回答,我找到了奇怪行为的根源。
"undefined" 比较的最后一步是比较对象 types 的内存位置。在本地和 CI 服务器上比较 id(type(thing))
和 id(type(0.02))
后,我发现 Thing
的 id 在本地总是较高,而在 CI 上总是较低服务器!
这个问题是由拒绝在本地失败的失败测试引起的,并且只会在我们的 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
的值来看,它们与比较结果根本不相关。
更新:
只有当 class 继承自 dict
时才会出现这种不同的行为。当它继承自 object
时,两个安装的比较行为相同,并给出与上述本地结果相同的结果。
更新 2:
我刚刚发现我可以仅使用 __int__
和 __gt__
方法进一步简化测试用例,但是如果我删除其中任何一个方法,那么奇怪的行为就会消失。
如评论中所述,dict
已经定义了所有比较运算符。 documented 行为是:
Objects of different types, except different numeric types and different string types, never compare equal; such objects are ordered consistently but arbitrarily
换句话说,dicts被专门定义为允许与其他类型进行比较,但这种比较的结果是未定义的。 (这在 Python 3 中已更改,因此不再允许此类类型间比较。)
当您只为您的类型重写一些比较运算符时,事情会变得更加复杂。由于您的类型定义了 __gt__
但未定义 __lt__
,因此 thing > 0.01
将使用您的自定义 __gt__
,但 thing < 0.01
将使用默认(未定义)比较行为。因此,您得到的类型有时会使用确定性规则,有时会给出未定义的行为,具体取决于您使用的比较运算符。我不知道为什么你会看到你所看到的精确结果模式,但最重要的是你的 class 依赖于未定义的行为,所以你不能期望使用这种类型的比较有任何一致性。 Python 的两个实现可能在某些神秘的实现级别上做不同的事情,从而产生不同的未定义行为。未定义行为的要点是您不应该知道它是如何工作的(否则您可能会开始依赖它)。
顺便说一句,total_ordering
这里是一个空操作,如果你删除它,行为应该是一样的。 total_ordering
只添加尚未定义的比较运算符,但 dict
已经定义了所有这些,因此 total_ordering
不会做任何事情。如果您想在已经定义了自己的比较行为(如字典)的类型的子 class 上建立自己的排序关系,那么您需要手动覆盖每个单独的比较运算符。
经过进一步调查,根据@BrenBarn 的精彩回答,我找到了奇怪行为的根源。
"undefined" 比较的最后一步是比较对象 types 的内存位置。在本地和 CI 服务器上比较 id(type(thing))
和 id(type(0.02))
后,我发现 Thing
的 id 在本地总是较高,而在 CI 上总是较低服务器!