从最近的一个SO问题(参见Create a dictionary in python which is indexed by lists)我意识到我可能对python中可散列和不可变对象的含义有一个错误的概念。
答案 0 :(得分:83)
Hashing是以可重复的方式将一些大量数据转换为更小量(通常是单个整数)的过程,以便可以在表中以常量时间查找它({{ 1}}),这对于高性能算法和数据结构非常重要。
Immutability是一个对象在创建后不会以某种重要方式改变的想法,尤其是以任何可能改变该对象的哈希值的方式。
这两个想法是相关的,因为用作散列键的对象通常必须是不可变的,因此它们的散列值不会改变。如果允许更改,那么该对象在数据结构(如哈希表)中的位置会发生变化,然后效率散列的整个目的就会失效。
要真正理解您应该尝试使用C / C ++等语言实现自己的哈希表,或者阅读O(1)
类的Java实现。
答案 1 :(得分:10)
- 是否有可变对象是可散列的或不可变的对象?
在Python中,元组是不可变的,但只有当它的所有元素都是可哈希的时候它才可以清除。
NULL
可灌输类型
答案 2 :(得分:8)
从技术上讲,hashable意味着该类定义__hash__()
。根据文件:
__hash__()
应该返回一个整数。唯一需要的属性是比较相等的对象具有相同的哈希值;建议以某种方式将对象组件的哈希值混合在一起(例如使用exclusive或),这些哈希值也是对象比较中的一部分。
我认为对于Python内置类型,所有可清除类型也是不可变的。
虽然有一个可变对象,但仍然难以定义__hash__()
,但也许并非不可能。
答案 3 :(得分:7)
如果对象具有在其生命周期内永远不会更改的哈希值(它需要
__hash__()
方法),并且可以与其他对象进行比较(它需要__eq__()
或{{ 1}}方法)。比较相等的Hashable对象必须具有相同的哈希值。Hashability使对象可用作字典键和set成员,因为这些数据结构在内部使用哈希值。
所有Python的不可变内置对象都是可清除的,而没有可变容器(例如列表或字典)。默认情况下,作为用户定义类实例的对象是可清除的;它们都比较不相等,它们的哈希值是它们的id()。
Dicts和sets必须使用哈希来在哈希表中进行有效查找;散列值必须是不可变的,因为更改散列会弄乱数据结构并导致dict或set失败。使哈希值不可变的最简单方法是使整个对象不可变,这就是为什么两者经常在一起提到的原因。
虽然没有内置的可变对象是可以清除的,但是可以使用不可变的哈希值来创建一个可变对象。通常只有对象的一部分表示其标识,而对象的其余部分包含可自由更改的属性。只要哈希值和比较函数基于身份而不是可变属性,并且身份永远不会改变,您就满足了要求。
答案 4 :(得分:4)
由于
之间的相互作用,即使在不可变和可散列之间没有明确的关系,也存在隐含的含义。除非您重新定义__eq__
,否则此处没有问题,因此对象类定义了值的等效性。
一旦你完成了这个,你需要找到一个稳定的哈希函数,它总是为表示相同值的对象返回相同的值(例如,__eq__
)返回True,并且在生命周期内永远不会改变一个对象。
很难看到可能的应用程序,请考虑满足这些要求的可能类 A 。虽然有明显的退化情况__hash__
返回常量。
现在: -
>>> a = A(1)
>>> b = A(1)
>>> c = A(2)
>>> a == b
True
>>> a == c
False
>>> hash(a) == hash(b)
True
>>> a.set_value(c)
>>> a == c
True
>>> assert(hash(a) == hash(c)) # Because a == c => hash(a) == hash(c)
>>> assert(hash(a) == hash(b)) # Because hash(a) and hash(b) have compared equal
before and the result must stay static over the objects lifetime.
实际上这意味着在创建hash(b)== hash(c)时,尽管事实上从未比较过相等。无论如何,我都很难看到有用地定义__hash__
()的可变对象,它定义了按值比较。
注意:__lt__
,__le__
,__gt__
和__ge__
比较不受影响,因此您仍然可以定义可清除对象的排序,可变的或基于其价值的其他方式。
答案 5 :(得分:3)
仅仅是因为这是谷歌的热门话题,这里有一个简单的方法可以使可变对象变为可用:
>>> class HashableList(list):
... instancenumber = 0 # class variable
... def __init__(self, initial = []):
... super(HashableList, self).__init__(initial)
... self.hashvalue = HashableList.instancenumber
... HashableList.instancenumber += 1
... def __hash__(self):
... return self.hashvalue
...
>>> l = [1,2,3]
>>> m = HashableList(l)
>>> n = HashableList([1,2,3])
>>> m == n
True
>>> a={m:1, n:2}
>>> a[l] = 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> m.hashvalue, n.hashvalue
(0, 1)
我实际上在创建一个类时会发现这样的用法,以便将SQLAlchemy记录转换为可变的并且对我更有用的东西,同时保持它们作为dict键使用的可用性。
答案 6 :(得分:3)
不可变意味着对象在其生命周期内不会以任何重要方式发生变化。这是编程语言中一个含糊但常见的想法。
可持续性略有不同,并指比较。
hashable 如果对象具有永远不会的哈希值,则该对象是可清除的 在其生命周期中的变化(它需要
__hash__()
方法),并且可以 与其他对象相比(它需要__eq__()
或__cmp__()
方法)。 比较相等的Hashable对象必须具有相同的哈希值。
所有用户定义的类都有__hash__
方法,默认情况下只返回对象ID。因此,满足可持续性标准的对象不一定是不可变的。
您声明的任何新类的对象都可以用作字典键,除非您通过例如从__hash__
抛出
我们可以说所有不可变对象都是可清除的,因为如果哈希在对象的生命周期中发生变化,那么就意味着该对象发生了变异。
但不完全。考虑一个有一个列表(可变)的元组。有人说元组是不可变的,但与此同时它有点不可用(抛出)。
d = dict()
d[ (0,0) ] = 1 #perfectly fine
d[ (0,[0]) ] = 1 #throws
可持续性和不变性是指对象实例,而不是类型。例如,类型为tuple的对象可以是可以清洗的。
答案 7 :(得分:1)
在Python中,它们大多可以互换;因为哈希应该代表内容,所以它和对象一样可变,并且让对象更改哈希值会使它无法用作dict键。
在其他语言中,哈希值与对象的“身份”更相关,而不是(必然)与值相关。因此,对于可变对象,指针可用于启动散列。当然,假设一个对象不会在内存中移动(就像某些GC那样)。例如,这是Lua中使用的方法。这使得可变对象可用作表键;但是为新手创造了几个(令人不快的)惊喜。
最后,拥有一个不可变的序列类型(元组)使得“多值键”更好。
答案 8 :(得分:0)
Hashable意味着变量的值可以用常量 - 字符串,数字等来表示(或者更确切地说,编码)。现在,某些可能发生变化(可变)的东西不能用不可能的东西来表示。因此,任何可变的变量都不能被清除,并且同样地,只有不可变的变量可以被清除。
希望这会有所帮助......