Python:`键不在my_dict`中,而是键在my_dict.keys()中

时间:2010-09-13 14:11:14

标签: python dictionary hash

我有一种奇怪的情况。我有一个词典,self.containing_dict。使用调试探针,我看到dict的内容,我可以看到self是它的关键。但看看这个:

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

发生了什么事?

(我会注意到这是在弱片回调中执行的一段代码。)

更新:我被要求显示__hash__ self的实施情况。这是:

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 :(得分:5)

您描述的问题只能由self实施__eq__(或__cmp__)而未实施附带的__hash__引起。如果你没有实现__hash__方法,你应该这样做 - 通常你不能使用定义__eq__而不是__hash__的对象作为dict键,但如果你继承了__hash__可能会漏掉。

如果你实现了__hash__,你必须确保它以正确的方式运行:结果不得在对象的生命周期内改变(或者至少只要对象作为一个dict使用键或设置项),它必须与__eq__一致。对象的哈希值必须与它等于的对象相同(根据其__eq____cmp__。)对象的哈希值可能是不同于它不等于的物体,但它不一定是。这些要求也意味着你不能在对象的生命周期内改变__eq__的结果,这就是为什么可变对象通常不能用作dict键的原因。

如果您的__hash____eq__不匹配,Python将无法在dicts和sets中找到该对象,但它仍会显示在dict.keys()和{ {1}},这就是你在这里描述的内容。实现list(set)方法的常用方法是返回您在__hash__hash()方法中使用的任何属性的__eq__

答案 1 :(得分: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通过相等性检查每个密钥,绕过哈希检查。

答案 2 :(得分:0)

最有可能的是,您为任何类self定义了自定义哈希和比较,并且在将其添加到词典后对self进行了变异。

如果你使用一个可变对象作为字典键,那么在你改变之后你可能无法在字典中访问它,但它仍然会出现在keys()结果中。