【问题标题】:How can "k in d" be False, but "k in d.keys()" be True?“k in d”怎么可能是假的,但“k in d.keys()”怎么会是真的呢?
【发布时间】:2010-10-27 17:50:10
【问题描述】:

我有一些抛出 KeyError 异常的 Python 代码。到目前为止我还没有能够在运行环境之外复现,所以我不能在这里发布一个简化的测试用例。

引发异常的代码是通过这样的循环迭代:

for k in d.keys():
    if condition:
        del d[k]

del[k] 行引发异常。我在它周围添加了一个try/except 子句,并且能够确定k in d 为假,但k in d.keys() 为真。

d 的键是旧式类实例的绑定方法。

该类实现了__cmp____hash__,所以这就是我一直关注的地方。

【问题讨论】:

  • 好吧,如果你现在是什么k 导致了问题,你为什么不看看它是否存在于d.keys()d 中?
  • 让我澄清一下,如果您忽略迭代并仅测试字典,那么有一个键 k in d 为真但k in d.keys() 为假? IE。迭代与问题无关?
  • 你能不能也展示一下你的__hash__函数?
  • @Charles:但他没有遍历字典!

标签: python dictionary python-2.x


【解决方案1】:

k in d.keys() 将迭代地测试每个键的相等性,而 k in d 使用 __hash__,因此您的 __hash__ 可能会被破坏(即它为比较相等的对象返回不同的哈希值)。

【讨论】:

  • __hash__ 是 buildbot 的 buildbot.util.ComparableMixin.__hash__,它查看实例的 compare_attrs 以生成哈希值。这些值会随着时间而变化,因此对象的哈希值在其生命周期内并不稳定。
  • 是的,这是破坏__hash__ 的另一种方式。但是,将对象用作字典键甚至没有意义。
  • 是的,幸运的是,不是我的代码将对象作为字典键(un)。
【解决方案2】:

出于兴趣的简单示例:

>>> count = 0
>>> class BrokenHash(object):
...     def __hash__(self):
...             global count
...             count += 1
...             return count
...
...     def __eq__(self, other):
...             return True
...
>>> foo = BrokenHash()
>>> bar = BrokenHash()
>>> foo is bar
False
>>> foo == bar
True
>>> baz = {bar:1}
>>> foo in baz
False
>>> foo in baz.keys()
True

【讨论】:

    【解决方案3】:

    在迭代 d 时不要删除项目,将要删除的键存储在列表中并在另一个循环中删除它们:

    deleted = []
    for k in d.keys():
        if condition:
            deleted.append(k)
    for k in deleted:
        del d[k]
    

    【讨论】:

    • 他不是在迭代 d,而是在迭代 d.keys() 列表
    • @SilentGhost 如果键是延迟加载的(我怀疑它们是但目前无法确认),那么在这种情况下它实际上是相同的。
    • @懒加载是什么意思?这是一个列表。
    • @SilentGhost 你是对的,除非他在 python 3.x 上,其中 dict.keys() 返回一个迭代器。另外,也许他在测试condition 时会修改d
    • 这也不能解决问题
    【解决方案4】:

    您正在做的事情会在 Java 中引发并发修改异常。 d.keys() 会在您调用它时创建一个密钥列表,但该列表现在是静态的 - 对 d 的修改不会更改 d.keys() 的存储版本。因此,当您迭代 d.keys() 但删除项目时,您最终可能会修改不再存在的密钥。

    您可以使用d.pop(k, None),如果k 不存在,它将返回映射到kNone 的值。这避免了KeyError 问题。

    编辑:为了澄清,为了防止更多的幻影降级(负面反馈没有问题,只需使其具有建设性并发表评论,这样我们就可以进行潜在的信息性讨论 - 我在这里学习和帮助):

    确实,在这种特殊情况下,它不应该搞砸。我只是把它作为一个潜在的问题提出来,因为如果他在程序的另一部分使用相同类型的编码方案,他对他如何处理数据结构如此小心/幸运,可能会出现这样的问题。他甚至没有使用字典,而是使用一个实现某些方法的类,以便您可以以类似的方式处理它。

    【讨论】:

    • 不,这不是真的——如果你只在d.keys() 中使用del d[k]k,你永远不会删除一个键两次,因为键是唯一的。
    • 假设一切都像他上面那样,键在字典中是唯一的,他每次迭代只删除一次键,所以他没有删除他已经删除的键。
    • 嗯,是的,确实如此,在这种特殊情况下,它不应该搞砸。我只是把它作为一个潜在的问题提出来。他甚至没有使用字典,而是使用了一个实现某些方法的类。
    • 你的答案是错误的,这就是我对你投反对票的原因。我讨厌人们给出明显错误的答案,或者只是猜测。如果你不知道答案,请不要在墙上乱猜,没有用。
    猜你喜欢
    • 1970-01-01
    • 2011-06-13
    • 1970-01-01
    • 1970-01-01
    • 2015-01-03
    • 2013-07-14
    • 2011-02-06
    • 1970-01-01
    • 2010-11-22
    相关资源
    最近更新 更多