Python 3.2中如何实现__hash__?

5

我想让自定义对象可哈希化(通过pickling)。我可以找到Python 2.x的__hash__算法(请参见下面的代码),但它显然与Python 3.2的哈希不同(我想知道为什么?)。有人知道Python 3.2中如何实现__hash__吗?

#Version: Python 3.2

def c_mul(a, b):
    #C type multiplication
    return eval(hex((int(a) * b) & 0xFFFFFFFF)[:-1])

class hs:
    #Python 2.x algorithm for hash from http://effbot.org/zone/python-hash.htm
    def __hash__(self):
        if not self:
            return 0 # empty
        value = ord(self[0]) << 7
        for char in self:
            value = c_mul(1000003, value) ^ ord(char)
        value = value ^ len(self)
        if value == -1:
            value = -2
        return value


def main():
    s = ["PROBLEM", "PROBLEN", "PROBLEO", "PROBLEP"]#, "PROBLEQ", "PROBLER", "PROBLES"]
    print("Python 3.2 hash() bild-in")
    for c in s[:]: print("hash('", c, "')=", hex(hash(c)),  end="\n")
    print("\n")
    print("Python 2.x type hash: __hash__()")
    for c in s[:]: print("hs.__hash__('", c, "')=", hex(hs.__hash__(c)),  end="\n")


if __name__ == "__main__":
    main()

OUTPUT:
Python 3.2 hash() bild-in
hash(' PROBLEM ')= 0x7a8e675a
hash(' PROBLEN ')= 0x7a8e6759
hash(' PROBLEO ')= 0x7a8e6758
hash(' PROBLEP ')= 0x7a8e6747


Python 2.x type hash: __hash__()
hs.__hash__(' PROBLEM ')= 0xa638a41
hs.__hash__(' PROBLEN ')= 0xa638a42
hs.__hash__(' PROBLEO ')= 0xa638a43
hs.__hash__(' PROBLEP ')= 0xa638a5c

1
@bzlm 链接 讨论 Python 2.x。 - Nikiton
1
如果您想使您的对象可哈希化,最简单的方法是将对象数据传递到元组中并使用hash()函数。 - Winston Ewert
@Winston Ewert,我觉得你是完全正确的。甚至更进一步:为什么我不把所有数据都封装在对象内部的元组中呢?我有什么遗漏吗?听起来太简单了... - Nikiton
@Nikton,如果你把所有的数据都放在元组里,你的代码就会有像self.data[2]这样的东西,而不是self.value,这会使代码更难读。因此,当数据直接有用时,我才会把它放在一个元组中。 - Winston Ewert
显示剩余5条评论
2个回答

5

它们为什么不同的答案在这里:

哈希值现在是新类型Py_hash_t的值,该类型的大小定义为与指针相同。以前它们是long类型,在一些64位操作系统上仍然只有32位长。

哈希还考虑计算新值,请看

 sys.hash_info 

关于字符串,您可以查看http://svn.python.org/view/python/trunk/Objects/stringobject.c?view=markup 的第1263行 string_hash(PyStringObject *a)。


http://svn.python.org/view/python/trunk/Objects/stringobject.c?view=markup,第1263行的string_hash(PyStringObject *a) - Pih
1
请注意,由于CPython开发已迁移到Mercurial:http://hg.python.org/cpython/file/default/Objects/unicodeobject.c,因此SVN存储库正在逐渐过时。还要注意,在Python 3中没有8位字符串类型,只有Unicode字符串。 - ncoghlan

3

我在源代码(unicodeobject.c)中查找了新的函数并在Python中重新构建了它。这是它:

def my_hash(string):
    x = ord(string[0]) << 7
    for c in string:
        x = (1000003 * x) ^ ord(c)
    x ^= len(string)
    needCorrection =  x & (1 << 65)
    x %= 2 ** 64
    if needCorrection:
        x = -~(-x ^ 0xFFFFFFFFFFFFFFFF)
    if x == -1:
        x = -2
    return x

这只支持64位,不过现在已经修正了Python在数字变为负数时的奇怪行为。(你最好不要想太多。)


谢谢!我认为我可以将其转换为32位。 - Nikiton
我甚至不确定是否可能在纯Python中实现该算法。问题与使while n: n>> = 1对于负数成为无限循环的相同问题相同:Python假装负数是无限长的,并用1填充前面。 - cemper93
你是对的,有些问题,对于""asdfasdf",我得到了2465956724作为my_hash和-1829010572作为hash()。我正在删除Edit1。 - Nikiton

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接