从最近的一个SO问题(参见在Python中创建由列表索引的字典),我意识到我可能对Python中"可哈希(hashable)"和"不可变(immutable)"对象的含义有误解。
- 实践中“可哈希”是什么意思?
- “可哈希”与“不可变”的关系是什么?
- 是否存在既是可变对象又是可哈希的对象,或者不可哈希的不可变对象?
从最近的一个SO问题(参见在Python中创建由列表索引的字典),我意识到我可能对Python中"可哈希(hashable)"和"不可变(immutable)"对象的含义有误解。
- 可变对象是否存在哈希化的情况?不可变对象是否有不可哈希化的情况?
在Python中,元组是不可变的,但仅当它的所有元素都是可哈希的时才可以哈希。
>>> tt = (1, 2, (30, 40))
>>> hash(tt)
8027212646858338501
>>> tl = (1, 2, [30, 40])
>>> hash(tl)
TypeError: unhashable type: 'list'
可哈希类型
类对象
。不可哈希的不可变对象:包含不可哈希元素的元组
,如列表、字典或集合。在元素级别上,集合成员和字典键必须是可哈希/不可变的。 - Leon Chang来自Python词汇表:
如果对象有一个永远不会在其生命周期内改变的哈希值(它需要一个
__hash__()
方法),并且可以与其他对象进行比较(它需要一个__eq__()
或__cmp__()
方法),则该对象是可哈希的。具有相同哈希值的可哈希对象必须相等。哈希性使对象可用作字典键和集合成员,因为这些数据结构在内部使用哈希值。
所有Python内置的不可变对象都是可哈希的,而没有可变容器(例如列表或字典)是可哈希的。用户定义类的实例默认情况下是可哈希的;它们都不相等,它们的哈希值是它们的
id()
。
在哈希表中进行高效查找,字典和集合必须使用哈希;哈希值必须是不可变的,因为更改哈希值将破坏数据结构并导致字典或集合失败。使哈希值不可变的最简单方法是使整个对象不可变,这就是为什么两者经常同时提到的原因。
虽然没有任何内置的可变对象是可哈希的,但是可以创建一个可变对象,其哈希值是不可变的。通常只有对象的一部分表示其标识,而对象的其余部分包含可以自由更改的属性。只要哈希值和比较函数基于标识而不是可变属性,并且标识从不更改,就满足要求。
id
)。这在对象的生命周期内不会改变,因此它是可哈希的,但这并不意味着您不能定义可变类型!抱歉,但哈希性并不意味着不可变性。 - Scott Griffiths__hash__()
。根据文档:
我认为对于Python内置类型,所有可哈希类型也都是不可变的。
__hash__()
应返回一个整数。唯一必需的属性是比较相等的对象具有相同的哈希值;建议以某种方式混合(例如使用异或)对象的组成部分的哈希值,这些部分也参与对象的比较。
__hash__()
。__hash__
被定义为返回对象的 id
;您必须费些周折才能设置 __hash__ = None
使其不可哈希。此外,正如 Mark Ransom 所提到的,还有一个额外的条件,即仅当哈希值永远不会改变时,它才是可哈希的! - Scott Griffiths即使没有显式强制不可变和可哈希之间的关系,由于以下两个因素的相互作用,它们之间仍然存在隐含关系:
除非您重新定义了__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.
Immutable指的是对象在其生命周期内不会发生任何重大变化。这是编程语言中一个模糊但常见的概念。
Hashable略有不同,它指的是比较。
可哈希如果一个对象具有哈希值(需要一个
__hash __()
方法),并且可以与其他对象进行比较(需要一个__eq__()
或__cmp__()
方法),则该对象是可哈希的,其哈希值在其生命周期内永远不会改变。比较相等的可哈希对象必须具有相同的哈希值。
所有用户定义的类都有__hash__
方法,默认情况下只返回对象ID。因此,符合哈希性标准的对象不一定是不可变的。
您声明的任何新类的对象都可以用作字典键,除非您通过例如从__hash__
抛出来防止它。
d = dict()
d[ (0,0) ] = 1 #perfectly fine
d[ (0,[0]) ] = 1 #throws
可哈希性和不可变性是指对象实例,而非类型。例如,元组类型的对象可以是可哈希的或不可哈希的。
comparison != identity
的情况是当比较“无效”的值时,例如 float("nan") == float("nan")
,或者来自切片的内部字符串:"apple" is "apple"
与 "apple" is "crabapple"[4:]
。 - sleblanc仅因为这是谷歌的热门搜索结果,这里提供一种简单的方法来使可变对象具有哈希值:
>>> 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记录转换为可变且对我更有用的东西,同时保持其可哈希性以便用作字典键。
在Python中,它们大多是可以互换的;因为哈希应该代表内容,所以它与对象一样可变,如果一个对象改变了哈希值,那么它就不能用作字典键。
在其他语言中,哈希值更多地与对象的“身份”相关,而不是(必然)与值相关。因此,对于可变对象,指针可以用于开始哈希。当然,假设对象不会在内存中移动(某些GC会这样做)。例如,Lua使用的就是这种方法。这使得可变对象可用作表键;但对于新手来说,这会产生几个(不愉快的)惊喜。
最终,拥有一个不可变序列类型(元组)使得“多值键”更加美好。
可哈希意味着变量的值可以被表示(或编码)为常量 - 字符串、数字等。现在,任何可变的东西都是易变的,不能用不变的东西来表示。因此,任何可变的变量都不能是可哈希的,同样地,只有不可变的变量才能是可哈希的。
希望这可以帮助到您...
HashMap
将出现故障:既不能找到旧键也不能找到新键,即使您打印地图,也可以在地图上看到键。 - user319799