如果我们制作这样的病态马铃薯:
>>> class Potato:
... def __eq__(self, other):
... return False
... def __hash__(self):
... return random.randint(1, 10000)
...
>>> p = Potato()
>>> p == p
False
我们可以通过这种方式打破集合和决策(注意:即使__eq__
返回True
也是如此,它与打破他们的哈希):
>>> p in {p}
False
>>> p in {p: 0}
False
同样len({p: 0, p: 0}) == 2
和{p: 0}[p]
引发KeyError,基本上所有与地图相关的内容都会如预期的那样消失。
但我没想到的是我们无法打破名单
>>> p in [p]
True
为什么?似乎list.__contains__
迭代,但在检查相等性之前它是checking identity。既然身份并不意味着相等(例如参见NaN对象),那么列表在身份比较中短路的原因是什么?
答案 0 :(得分:11)
list
,tuple
等确实在进行相等检查之前进行了身份检查,这种行为是由these invariants推动的:
assert a in [a]
assert a in (a,)
assert [a].count(a) == 1
for a in container:
assert a in container # this should ALWAYS be true
不幸的是,dict
s,set
和朋友通过哈希进行操作,所以如果你搞砸了那些,你确实可以有效地打破它们。
有关某些历史记录,请参阅this issue和this issue。
答案 1 :(得分:8)
一般而言,打破身份意味着平等的假设可以打破Python中的各种事物。确实NaN打破了这个假设,因此NaN在Python中打破了一些东西。讨论可以在this Python bug中找到。在Python 3.0的预发布版本中,删除了对此假设的依赖,但该错误的解决方案是将其重新放入(即,使Python 3提供与Python 2相同的行为,其中身份检查快捷方式是完成)。 Python 3的documentation正确地说:
对于容器类型,例如list,tuple,set,frozenset,dict或collections.deque,表达式
x in y
等同于any(x is e or x == e for e in y)
。
但是,似乎Python 2的文档不正确,因为它说:
对于列表和元组类型,当且仅当存在索引i使得x == y [i]为真时,y中的x才为真。
如果你愿意,你可以提出一个关于这个问题的文档错误,虽然这是一个非常深奥的问题,所以我怀疑它会在任何人的优先级列表上都很高。