更改__hash__的类仍可用于字典访问

时间:2016-07-14 12:11:10

标签: python python-3.x dictionary

我所做的显然不是人们想要做的事情,而是我只是测试为给定的类实现__hash__

我想看看是否在词典中添加了一个虚假的'hashable'类,然后更改它的哈希值会导致它无法访问它。

我的班级看起来像这样:

class PhonyHash:

    def __hash__(self):
        val = list("A string")
        return id(val)  # always different

在我的IPython控制台中执行以下操作:

>>> p = PhonyHash()
>>> d = { p: "a value"}
>>> hash(p)  # changes hash

然后尝试使用d[p]访问元素:

>>> d[p]
"a value"

我明白,这不是应该做的事情,我真的很好奇为什么它有效。 dict不使用对象的hash()来存储/检索它吗?为什么这有效?

编辑,正如@VPfB sets的评论中所述,出于某种原因符合预期:

>>> p = PhonyHash()
>>> s = {p}
>>> p in s
False

2 个答案:

答案 0 :(得分:5)

这是一个奇怪的命运。几台CPython机器挫败了你。现在的三个问题是:

  1. 支持dict的数组的初始大小为8 [1]
  2. CPython中的所有对象都有以8为模的内存地址 [2]
  3. dict类有一个优化,它检查键是同一个对象,如果为true则停在那里(否则它将根据__eq__方法检查它们是否相等) [3]
  4. 这意味着尽管您的对象始终生成不同的哈希值,但将要检查的后备阵列的第一个槽是相同的。如果您没有收到密钥错误,因为广告位是空的。 dict然后决定它有正确的键,因为它具有完全相同的对象,而不仅仅是一个相等的对象。

    class PhonyHash:
        _hash = 1
        def __hash__(self):
            return self._hash
    
    p = PhonyHash()
    d = {p: "val"}
    print(p in d) # True
    p._hash = 2
    print(p in d) # False
    p._hash = 9 # 9 % 8 == 1
    print(p in d) # True
    

    指向CPython源的链接

    1. dict struct定义ma_table,其开头为ma_smalltable,其长度为PyDict_MinSize
    2. 这在Objects/obmalloc.c
    3. 中有记录
    4. 可以在查找功能herehere
    5. 中看到

答案 1 :(得分:3)

我有一个可能的解释:

根据这个来源:http://www.laurentluce.com/posts/python-dictionary-implementation/当持有dict元素的表很小时,只使用散列的最后几位。

id()号通常是一个机器地址,很可能是一些内存地址边界。所以最后几位总是为零,而不是随机的。在结果中,您总是遇到table [0]元素。

尝试使用随机虚假哈希的不同来源会改变这种情况,并抛出预期的KeyError。

编辑:Dunes以同样的方式回答了问题,他比我快。