哈希在python中做了啥?

Posted

技术标签:

【中文标题】哈希在python中做了啥?【英文标题】:What does hash do in python?哈希在python中做了什么? 【发布时间】:2013-07-09 06:33:51 【问题描述】:

我看到了一个将hash 函数应用于元组的代码示例。结果它返回一个负整数。我想知道这个功能有什么作用?谷歌没有帮助。我找到了一个解释如何计算哈希的页面,但它没有解释为什么我们需要这个函数。

【问题讨论】:

你看过docs... 转到此链接(官方文档)。它指定了一切。去link! 我喜欢这个问题不是重复“它是什么”而是“我们为什么需要它”。 官方链接很混乱 【参考方案1】:

A hash is an fixed sized integer that identifies a particular value。每个值都需要有自己的哈希值,因此对于相同的值,即使不是同一个对象,您也会得到相同的哈希值。

>>> hash("Look at me!")
4343814758193556824
>>> f = "Look at me!"
>>> hash(f)
4343814758193556824

哈希值的创建方式需要使结果值均匀分布,以减少您获得的哈希冲突次数。哈希冲突是指两个不同的值具有相同的哈希。因此,相对较小的更改通常会导致非常不同的哈希值。

>>> hash("Look at me!!")
6941904779894686356

这些数字非常有用,因为它们可以在大量值集合中快速查找值。它们的两个使用示例是 Python 的 setdict。在list 中,如果要检查一个值是否在列表中,对于if x in values:,Python 需要遍历整个列表并将x 与列表values 中的每个值进行比较。这可能需要很长时间list。在 set 中,Python 会跟踪每个哈希值,当您键入 if x in values: 时,Python 将获取 x 的哈希值,在内部结构中查找它,然后仅将 x 与值进行比较具有与x 相同的哈希值。

字典查找使用相同的方法。这使得setdict 中的查找非常快,而list 中的查找速度很慢。这也意味着您可以在list 中拥有不可散列的对象,但不能在set 中或作为dict 中的键。不可散列对象的典型示例是任何可变对象,这意味着您可以更改其值。如果你有一个可变对象,它不应该是可散列的,因为它的散列会在其生命周期内发生变化,这会引起很多混乱,因为一个对象最终可能会在字典中使用错误的散列值。

请注意,一个值的哈希值只需要在 Python 的一次运行中相同。在 Python 3.3 中,它们实际上会随着 Python 的每次新运行而改变:

$ /opt/python33/bin/python3
Python 3.3.2 (default, Jun 17 2013, 17:49:21) 
[GCC 4.6.3] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> hash("foo")
1849024199686380661
>>> 
$ /opt/python33/bin/python3
Python 3.3.2 (default, Jun 17 2013, 17:49:21) 
[GCC 4.6.3] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> hash("foo")
-7416743951976404299

这是为了更难猜测某个字符串将具有什么哈希值,这是 Web 应用程序等的重要安全功能。

因此不应永久存储哈希值。如果您需要以永久方式使用哈希值,您可以查看更“严重”的哈希类型 cryptographic hash functions,它可用于对文件进行可验证的校验和等。

【讨论】:

关于潜在的哈希冲突:hash(-1) == hash(-2) (runnin Python 2.7) 我正在运行 Python 3.6.1 并且存在冲突。 hash(-1) == hash(-2) 今天仍然存在。幸运的是,它不会对字典和集合查找产生不利影响。除-1 外,所有其他整数i 自行解析为hash(i)【参考方案2】:

TL;DR:

请参考the glossary:hash()用作比较对象的快捷方式,如果一个对象可以与其他对象进行比较,则认为它是可哈希的。这就是我们使用hash() 的原因。它还用于访问dictset 元素,这些元素实现为resizable hash tables in CPython。

技术考虑

通常比较对象(可能涉及多个递归级别)是昂贵的。 最好是hash() 函数便宜一个(或几个)数量级。 比较两个哈希比比较两个对象更容易,这就是快捷方式所在。

如果你读到how dictionaries are implemented,他们使用哈希表,这意味着从对象派生一个键是在O(1) 的字典中检索对象的基石。然而,这非常依赖于您的散列函数是否防碰撞。字典中的worst case for getting an item实际上是O(n)

在这点上,可变对象通常是不可散列的。 hashable 属性意味着您可以将对象用作键。如果哈希值被用作键并且同一个对象的内容发生了变化,那么哈希函数应该返回什么?是同一个钥匙还是不同的钥匙?这取决于您如何定义散列函数。

实例学习:

假设我们有这个类:

>>> class Person(object):
...     def __init__(self, name, ssn, address):
...         self.name = name
...         self.ssn = ssn
...         self.address = address
...     def __hash__(self):
...         return hash(self.ssn)
...     def __eq__(self, other):
...         return self.ssn == other.ssn
... 

请注意:这一切都是基于个人的 SSN 永远不会改变的假设(甚至不知道在哪里可以从权威来源实际验证该事实)。

我们还有鲍勃:

>>> bob = Person('bob', '1111-222-333', None)

鲍勃去见法官改名:

>>> jim = Person('jim bo', '1111-222-333', 'sf bay area')

这是我们所知道的:

>>> bob == jim
True

但这是两个不同的对象,分配了不同的内存,就像同一个人的两条不同的记录:

>>> bob is jim
False

现在是 hash() 派上用场的部分:

>>> dmv_appointments = 
>>> dmv_appointments[bob] = 'tomorrow'

猜猜看:

>>> dmv_appointments[jim] #?
'tomorrow'

您可以从两条不同的记录访问相同的信息。 现在试试这个:

>>> dmv_appointments[hash(jim)]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 9, in __eq__
AttributeError: 'int' object has no attribute 'ssn'
>>> hash(jim) == hash(hash(jim))
True

刚刚发生了什么?那是碰撞。因为hash(jim) == hash(hash(jim)) 都是整数,所以我们需要将__getitem__ 的输入与所有碰撞的项目进行比较。内置的int 没有ssn 属性,所以它会跳闸。

>>> del Person.__eq__
>>> dmv_appointments[bob]
'tomorrow'
>>> dmv_appointments[jim]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: <__main__.Person object at 0x7f611bd37110>

在最后一个示例中,我展示了即使发生碰撞,也会执行比较,对象不再相等,这意味着它成功引发了KeyError

【讨论】:

非常方便的解释。作为一个新手,这帮助我弄清楚了如何创建可以放入集合并将它们用作字典/哈希表的键的类。此外,如果我执行 collection[hashable_obj] = hashable_obj,我稍后可以获得指向该实例的指针。但是请告诉我是否有更好的方法来跟踪此类收藏。 @dnozay 但是,hash() 的输出仍然是一个固定大小的整数,可能会导致冲突 有人能详细说明上面例子中__eq__的用法吗?当它试图将它接收到的键与它拥有的所有键进行比较时,它是否被字典调用?这样通过del 上一个示例中的__eq__ 方法,字典没有可调用的内容来确定它收到的密钥与它拥有的密钥的等效性? @JetBlue 在带有键 hash(jim) 的示例中,“碰撞”解释不完整。调用Person.__eq__ 是因为现有密钥与hash(jim) 具有相同的哈希值,以确保使用正确的密钥Person.__eq__。它出错是因为它假定other(即int)具有ssn 属性。如果字典中不存在hash(jim) 键,则不会调用__eq__。这解释了何时键查找可以是 O(n):当所有项目具有相同的散列时 __eq__ 必须在所有项目上使用,例如在 key 不存在的情况下。 虽然我理解你的例子的教学兴趣,但写dmv_appointments[bob.ssn] = 'tomorrow' 会不会更简单,不需要定义__hash__ 方法?我知道你写和读的每个约会都会增加 4 个字符,但对我来说似乎更清楚。【参考方案3】:

Python docs for hash() 状态:

哈希值是整数。它们用于在字典查找期间快速比较字典键。

Python 字典被实现为哈希表。因此,每当您使用字典时,都会在您传入的键上调用 hash() 以进行分配或查找。

另外,docs for the dict type 状态:

不可散列的值,即包含列表、字典或其他可变类型的值(按值而不是对象标识进行比较)不能用作键。

【讨论】:

【参考方案4】:

哈希被字典和集合用来快速查找对象。一个很好的起点是***关于hash tables 的文章。

【讨论】:

【参考方案5】:

您可以在 python 中使用Dictionary 数据类型。它与散列非常相似——它也支持嵌套,类似于嵌套散列。

例子:

dict = 'Name': 'Zara', 'Age': 7, 'Class': 'First'
dict['Age'] = 8; # update existing entry
dict['School'] = "DPS School" # Add new entry

print ("dict['Age']: ", dict['Age'])
print ("dict['School']: ", dict['School'])

更多信息,请参考tutorial on the dictionary data type。

【讨论】:

以上是关于哈希在python中做了啥?的主要内容,如果未能解决你的问题,请参考以下文章

@符号在iPython / Python中做了啥[重复]

^= 在 python 中做了啥[关闭]

〜运算符在Python中做了啥[重复]

built-in method select 在 Python 3 中做了啥?

对象的 __init__() 方法在 python 中做了啥? [复制]

retranslateUi 在 PyQT 中做了啥