Python: `key不在my_dict`但`key在my_dict.keys()`中

4 投票
3 回答
2316 浏览
提问于 2025-04-16 04:05

我遇到了一个奇怪的情况。我有一个字典,叫做 self.containing_dict。通过调试工具,我可以看到这个字典的内容,并且发现 self 是它的一个键。但是看看这个:

>>> self in self.containing_dict
False
>>> self in self.containing_dict.keys()
True
>>> self.containing_dict.has_key(self)
False

这是怎么回事呢?

(我想说明一下,这段代码是在一个弱引用回调中执行的。)

更新:有人让我展示 self__hash__ 实现。这里是:

def __hash__(self):
    return hash(
        (
            tuple(sorted(tuple(self.args))),
            self.star_args,
            tuple(sorted(tuple(self.star_kwargs)))
        )
    )

args = property(lambda self: dict(self.args_refs))

star_args = property(
    lambda self:
        tuple((star_arg_ref() for star_arg_ref in self.star_args_refs))
)

star_kwargs = property(lambda self: dict(self.star_kwargs_refs))    

3 个回答

0

很可能你为某个类(也就是self所代表的类)定义了自定义的哈希和比较方法,而且在把self放进字典后,你对它进行了修改。

如果你把一个可变对象当作字典的键,然后在修改了这个对象后,你可能就无法在字典中找到它了,但它仍然会出现在keys()的结果里。

2

根据你写的 __hash__ 方法,这个类是通过存储它的参数的引用来生成哈希值的。问题在于,这些参数是和创建这个对象的代码共享的。如果这些参数被修改了,哈希值也会改变,这样你就无法在任何包含这个对象的字典中找到它了。

这些参数其实不需要复杂,简单的列表就可以了。

In [13]: class Spam(object) :
   ....:     def __init__(self, arg) :
   ....:         self.arg = arg
   ....:     def __hash__(self) :
   ....:         return hash(tuple(self.arg,))

In [18]: l = range(5)

In [19]: spam = Spam(l)

In [20]: hash(spam)
Out[20]: -3958796579502723947

如果我修改了作为参数传入的列表,哈希值也会随之改变。

In [21]: l += [10]

In [22]: hash(spam)
Out[22]: -6439366262097674983

因为字典的键是通过哈希值来组织的,当我执行 x in d 时,Python 首先会计算 x 的哈希值,然后在字典中查找这个哈希值对应的内容。问题是,如果一个对象在放入字典后哈希值发生了变化,Python 就会查看新的哈希值,而找不到原来的键。为了避免这个问题,使用键的列表会强制 Python 逐个检查每个键的相等性,而不依赖哈希值的检查。

5

你所描述的问题可能是因为你的对象实现了 __eq__(或者 __cmp__),但没有实现相应的 __hash__ 方法。如果你没有实现 __hash__ 方法,建议你去实现一个。通常情况下,定义了 __eq__ 但没有 __hash__ 的对象不能作为字典的键,但如果你继承了一个 __hash__ 方法,这个限制可能会被绕过。

如果你实现了 __hash__,你需要确保它的行为是正确的:返回的结果在对象的生命周期内不能改变(至少在对象作为字典键或集合项使用期间),而且它必须和 __eq__ 一致。一个对象的哈希值 必须 和它相等的对象(根据 __eq____cmp__)是相同的。一个对象的哈希值 可以 和不相等的对象不同,但不一定要这样。这个要求还意味着你不能让 __eq__ 的结果在对象的生命周期内改变,这就是为什么可变对象通常不能作为字典的键。

如果你的 __hash____eq__ 不匹配,Python 就无法在字典和集合中找到这个对象,但它仍然会出现在 dict.keys()list(set) 中,这就是你在这里描述的情况。实现 __hash__ 方法的常见方式是返回你在 __eq____cmp__ 方法中使用的属性的 hash() 值。

撰写回答