使对象x使得"x in [x]"返回False

wim*_*wim 25 python python-internals

如果我们制作这样的病态土豆:

>>> class Potato:
...     def __eq__(self, other):
...         return False
...     def __hash__(self):
...         return random.randint(1, 10000)
... 
>>> p = Potato()
>>> p == p
False
Run Code Online (Sandbox Code Playgroud)

我们可以通过这种方式打破集合和切换(注意:即使__eq__返回True它也是一样的,它正在破坏它们的哈希):

>>> p in {p}
False
>>> p in {p: 0}
False
Run Code Online (Sandbox Code Playgroud)

此外len({p: 0, p: 0}) == 2,并{p: 0}[p]引发KeyError,基本上所有与映射相关的东西都会出现,如预期的那样.

但我没想到的是我们不能打破名单

>>> p in [p]
True
Run Code Online (Sandbox Code Playgroud)

这是为什么?它似乎是list.__contains__迭代,但它首先在检查相等之前检查身份.由于不是身份意味着相等的情况(例如参见NaN对象),列表在身份比较中短路的原因是什么?

Eth*_*man 11

list,tuple等等,不相等性检查前确实做了一个身份检查,而这种行为是由动机这些不变量:

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
Run Code Online (Sandbox Code Playgroud)

不幸的是,dicts,sets和朋友通过哈希来操作,所以如果你搞砸那些你确实可以有效地打破它们.

有关某些历史记录,请参阅此问题此问题.


Bre*_*arn 8

一般来说,打破身份意味着平等的假设可以打破Python中的各种事物.确实NaN打破了这个假设,因此NaN在Python中打破了一些东西.讨论可以在这个Python bug中找到.在Python 3.0的预发布版本中,删除了对此假设的依赖,但该错误的解决方案是将其重新放入(即,使Python 3提供与Python 2相同的行为,其中身份检查快捷方式是完成).Python 3 的文档正确地说:

对于容器类型,例如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才为真.

如果你愿意的话,你可以提出一个关于这个的文档错误,虽然这是一个非常深奥的问题,所以我怀疑它在任何人的优先级列表上都会很高.

  • 作为OP文档,例如`set`的3.x中的文档是不是错了?检查哈希,而不是身份. (2认同)
  • @jonrsharpe:[创建了bug](http://bugs.python.org/issue23987) - 至少会记录解决方案. (2认同)