我看到了一个将hash
函数应用于元组的代码示例。结果,它返回一个负整数。我想知道这个功能做什么?Google没有帮助。我找到了一个页面,该页面解释了哈希的计算方式,但没有解释为什么我们需要此函数。
我看到了一个将hash
函数应用于元组的代码示例。结果,它返回一个负整数。我想知道这个功能做什么?Google没有帮助。我找到了一个页面,该页面解释了哈希的计算方式,但没有解释为什么我们需要此函数。
Answers:
哈希是固定大小的整数,用于标识特定值。每个值都需要有自己的哈希,因此对于相同的值,即使不是同一对象,您也将获得相同的哈希。
>>> hash("Look at me!")
4343814758193556824
>>> f = "Look at me!"
>>> hash(f)
4343814758193556824
散列值的创建方式应使结果值均匀分布,以减少所得到的散列冲突次数。哈希冲突是指两个不同的值具有相同的哈希。因此,相对较小的变化通常会导致完全不同的哈希值。
>>> hash("Look at me!!")
6941904779894686356
这些数字非常有用,因为它们可以在大量值中快速查找值。使用的两个示例是Pythonset
和dict
。在中list
,如果要检查列表中是否有值,请使用if x in values:
Python,需要遍历整个列表并x
与列表中的每个值进行比较values
。这可能需要很长时间list
。在其中set
,Python会跟踪每个哈希,当您键入时if x in values:
,Python会获取其哈希值x
,在内部结构中查找该哈希值,然后仅x
与具有相同哈希值的值进行比较x
。
字典查找使用相同的方法。这使得查找中set
和dict
速度非常快,而在查找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应用程序等的重要安全功能。
因此,哈希值不应永久存储。如果您需要永久使用哈希值,则可以查看更“严重”的哈希类型,即加密哈希函数,可用于生成文件的可验证校验和等。
hash(-1) == hash(-2)
(runnin Python 2.7)
hash(-1) == hash(-2)
今天仍然存在。幸运的是,它不会对字典和设置查找产生不利影响。除以外,所有其他整数都i
对它们自己进行解析。hash(i)
-1
请参阅词汇表:hash()
用作比较对象的快捷方式,如果可以将一个对象与其他对象进行比较,则该对象被视为可哈希的。这就是为什么我们使用hash()
。它还用于访问dict
和set
元素,这些元素和元素在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
。
hash()
是固定大小的整数,可能会导致冲突
__eq__
在上面的示例中详细说明的用法。尝试将接收到的密钥与它拥有的所有密钥进行比较时,由字典调用吗?这样,通过上一个示例中del
的__eq__
方法,字典中没有什么可用来确定接收到的密钥与拥有的密钥的等效性的东西吗?
hash(jim)
。Person.__eq__
之所以会调用,是因为现有密钥具有相同的哈希值,hash(jim)
以确保Person.__eq__
使用正确的密钥。之所以会出错是因为它假设other
,即int
,具有ssn
属性。如果hash(jim)
字典中不存在键,则__eq__
不会调用该键。这就解释了关键字查找何时可以为O(n)的情况:例如,当所有项目具有相同的哈希值时,__eq__
必须对所有项目使用相同的哈希值,例如,在关键字不存在的情况下。
dmv_appointments[bob.ssn] = 'tomorrow'
消除定义__hash__
方法的难度会不会更简单?我了解您每次编写和阅读的约会都会增加4个字符,但对我来说似乎更清晰。