哈希在python中做什么?


86

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


8
您看过文件吗?
TerryA

转到此链接(官方文档)。它指定了所有内容。去链接
裁缝_raj 2013年

2
我喜欢这个问题,不是“它是什么”的重复,而是“我们为什么需要它”。
dnozay

官方链接非常令人困惑
Rasmi Ranjan Nayak

Answers:


148

哈希是固定大小的整数,用于标识特定值。每个值都需要有自己的哈希,因此对于相同的值,即使不是同一对象,您也将获得相同的哈希。

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

散列值的创建方式应使结果值均匀分布,以减少所得到的散列冲突次数。哈希冲突是指两个不同的值具有相同的哈希。因此,相对较小的变化通常会导致完全不同的哈希值。

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

这些数字非常有用,因为它们可以在大量值中快速查找值。使用的两个示例是Pythonsetdict。在中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应用程序等的重要安全功能。

因此,哈希值不应永久存储。如果您需要永久使用哈希值,则可以查看更“严重”的哈希类型,即加密哈希函数,可用于生成文件的可验证校验和等。


11
关于潜在的哈希冲突:hash(-1) == hash(-2)(runnin Python 2.7)
Matthias 2013年

2
我正在运行Python 3.6.1,并且存在冲突。
The_Martian '18

hash(-1) == hash(-2)今天仍然存在。幸运的是,它不会对字典和设置查找产生不利影响。除以外,所有其他整数都i对它们自己进行解析。hash(i)-1
克里斯·康兰

35

TL; DR:

请参阅词汇表hash()用作比较对象的快捷方式,如果可以将一个对象与其他对象进行比较,则该对象被视为可哈希的。这就是为什么我们使用hash()。它还用于访问dictset元素,这些元素和元素在CPython中作为可调整大小的哈希表实现。

技术注意事项

  • 通常比较对象(可能涉及多个级别的递归)比较昂贵。
  • 优选地,该hash()函数便宜了一个数量级(或几个)。
  • 比较两个散列比比较两个对象要容易,这就是捷径所在。

如果您了解字典是如何实现的,它们将使用哈希表,这意味着从对象派生键是在中检索字典中对象的基石O(1)。但是,这很大程度上取决于您的哈希函数是否具有抗冲突性。实际上,在字典中获取项目最坏情况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))这两个都是整数btw,所以我们需要将的输入__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,则稍后可以获取指向该实例的指针。但是请告诉我是否有更好的方法来跟踪此类收藏。
PaulDong

@dnozay但是,输出仍然hash()是固定大小的整数,可能会导致冲突
过度兑换

2
有人可以__eq__在上面的示例中详细说明的用法。尝试将接收到的密钥与它拥有的所有密钥进行比较时,由字典调用吗?这样,通过上一个示例中del__eq__方法,字典中没有什么可用来确定接收到的密钥与拥有的密钥的等效性的东西吗?
捷蓝航空

1
@JetBlue在带有key的示例中,“ collosion”解释不完整hash(jim)Person.__eq__之所以会调用,是因为现有密钥具有相同的哈希值,hash(jim)以确保Person.__eq__使用正确的密钥。之所以会出错是因为它假设other,即int,具有ssn属性。如果hash(jim)字典中不存在键,则__eq__不会调用该键。这就解释了关键字查找何时可以为O(n)的情况:例如,当所有项目具有相同的哈希值时,__eq__必须对所有项目使用相同的哈希值,例如,在关键字不存在的情况下。
WloHu '18年

1
尽管我了解您的示例的教学兴趣,但是编写代码并dmv_appointments[bob.ssn] = 'tomorrow'消除定义__hash__方法的难度会不会更简单?我了解您每次编写和阅读的约会都会增加4个字符,但对我来说似乎更清晰。
亚历克西斯

3

状态的Python文档hash()

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

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

此外,类型状态的文档dict

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


1

字典使用散列并将其设置为快速查找对象。维基百科有关哈希表的文章是一个很好的起点。


-2

您可以Dictionary在python中使用数据类型。它非常类似于哈希-并且还支持嵌套,类似于嵌套哈希。

例:

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'])

有关更多信息,请参考有关字典数据类型的教程

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.