寫在前面
Hash
(哈希、散列)是一個將大體量數據轉化為很小數據的過程,甚至可以僅僅是一個數字,以便我們可以在O(1)
的時間復雜度下查詢它,所以,哈希對高效的算法和數據結構很重要。
immutable
(不可改變性)是指一些對象在被創建之后不會因為某些方式改變,特別是針對任何可以改變哈希對象的哈希值的方式。
由於hash key
必須是不可變(immutable
)的,對應的hash value
才能是不變,所以不可變
(immutable
)和可哈希
(hashable
)是有關系的。如果hash key
允許改變,那么像hashtable
這樣數據結構的對象將會改變,整個hash映射就都會失效。
具體以例來看,元組(tuple
)對象是不可變的(immutable
),字典(dict
)的鍵(key
)必須是可以哈希的(hashable
)。
hashable & unhashable
官方文檔:
如果一個對象在其生命周期內有一個固定不變的哈希值 (這需要__hash__()
方法) 且可以與其他對象進行比較操作 (這需要__eq__()
或 __cmp__()
方法) ,那么這個對象就是可哈希對象 (hashable
) 。可哈希對象必須有相同的哈希值才算作相等。
由於字典 (dict
) 的鍵 (key
) 和集合 (set
) 元素使用到了哈希值,所以只有可哈希 (hashable
) 對象才能被用作字典的鍵和集合的元素。
所有python內置的不可變(immutable
)對象(tuple
等)都是可哈希的,同時,可變容器 (比如:列表 (list
) 或者字典 (dict
) ) 都是不可哈希的。用戶自定義的類的實例默認情況下都是可哈希的;它們跟其它對象都不相等 (除了它們自己) ,它們的哈希值來自id()
方法。
mutable & immutable
(轉自 https://www.jianshu.com/p/49f940b2c03e)
首先要明白的是當我們在聊可變與不可變對象時,我們聊的是Python的內置對象。自己定義的對象通常我們不去討論它是不是可變的,畢竟Python本身是一門動態語言,需要的話我們隨時可以給自己定義的這個對象添加其它的屬性和方法。
提到Python內置的不可變對象我們能想到的往往有數字、字符串、元組等,提到Python內置的可變對象我們能想到的又有列表、字典等。我們是依據什么把其中的一些對象歸於可變,又把另一些歸於不可變的呢?
其實這種歸類的辦法很簡單:當我們改變一個對象的值的時候,如果能維持其id值不變,我們就說這個對象是可變,否則我們就說這個對象不可變。
實例檢測
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# unhashable 可變對象
# 如list、dict、set:同值不同址,不同值同址
# hashable 不可變對象
# 如int、str、char、tuple:同值同址,不同值不同址
# 怎么判斷可變不可變 ?
# 改個值,看id是不是一樣,id一樣的為可變,則不可哈希。id出現變化,則為不可變,可哈希
# list
L = [1, 2, 3]
L2 = [1, 2, 3]
print('id(L)', id(L))
print('id(L2)', id(L2))
L[0] = 4
print('id(L)', id(L)) # unhashable
'''--------------------- id(L) 2763485176456 id(L2) 2763485176520 id(L) 2763485176456 ---------------------'''
# dict
D = {'A':100, 'A-':90, 'B':80, 'C':70}
D2 = {'A':100, 'A-':90, 'B':80, 'C':70}
print('id(D)', id(D))
print('id(D2)', id(D2))
D['A'] = 99
print('id(D)', id(D)) # unhashable
'''--------------------- id(D) 2763485641608 id(D2) 2763485641680 id(D) 2763485641608 ---------------------'''
# set
S = set([1, 2, 3])
S2 = set([1, 2, 3])
print('id(S)', id(S))
print('id(S2)', id(S2))
S.remove(1)
print('id(S)', id(S)) # unhashable
'''--------------------- id(S) 1905131096776 id(S2) 1905131094088 id(S) 1905131096776 ---------------------'''
# int
a = 666
b = 666
print('hash(a):', hash(a))
print('hash(b):', hash(b))
print('id(a)', id(a))
print('id(b)', id(b))
a = 555
print('hash(a):', hash(a))
print('id(a)', id(a)) # hashable
'''--------------------- hash(a): 666 hash(b): 666 id(a) 1905130526128 id(b) 1905130526128 hash(a): 555 id(a) 1905131082192 ---------------------'''
# float
a = 1.2
b = 1.2
print('hash(a):', hash(a))
print('hash(b):', hash(b))
print('id(a)', id(a))
print('id(b)', id(b))
a = 1.1
print('hash(a):', hash(a))
print('id(a)', id(a)) # hashable
'''--------------------- hash(a): 461168601842738689 hash(b): 461168601842738689 id(a) 2682206597600 id(b) 2682206597600 hash(a): 230584300921369601 id(a) 2682206597624 ---------------------'''
# str
c = 'ZJ'
d = 'ZJ'
print('id(c)', id(c))
print('id(d)', id(d))
c = 'YF'
print('id(a)', id(c)) # hashable
'''--------------------- hash(c): 3106900240887856397 hash(d): 3106900240887856397 id(c) 1642181677384 id(d) 1642181677384 hash(c): -2749512413466868010 id(c) 1642181677608 ---------------------'''
# tuple
T = (1, 2, 3)
T2 = (1, 2, 3)
print('hash(T):', hash(T))
print('hash(T2):', hash(T2))
print('id(T)', id(T))
print('id(T2)', id(T2))
'''--------------------- hash(T): 2528502973977326415 hash(T2): 2528502973977326415 id(T) 2325794115656 id(T2) 2325794115656 ---------------------'''
# tuple2
T = (1, [1, 2], 3)
T2 = (1, [1, 2], 3)
print('id(T)', id(T))
print('id(T2)', id(T2))
T[1][0] = 4
print('id(T)', id(T)) # hashable
'''--------------------- id(T) 2979746926664 id(T2) 2979747407048 id(T) 2979746926664 ---------------------'''
# 補充說明
# 雖然字符串有個replace()方法,也確實變出了'Abc',但變量a最后仍是'abc'
a = 'abc'
print('a:', a)
print('a:', a.replace('a', 'A'))
print('a:', a)
'''--------------------- a: abc a: Abc a: abc ---------------------'''
# 這是因為 a.replace('a', 'A') 相當於
b = a.replace('a', 'A') # replace方法創建了一個新字符串'Abc'並返回
# Summary:
# 對於不變對象來說, 調用對象自身的任意方法, 也不會改變該對象自身的內容。
# 相反, 這些方法會創建新的對象並返回, 這樣, 就保證了不可變對象本身永遠是不可變的。
>>> class A:
... pass
...
>>> cls_a = A()
>>> cls_b = A()
>>> cls_a
<A object at 0x000001890DB69898>
>>> cls_b
<A object at 0x000001890DB6EDD8>
>>> cls_a.__hash__()
-9223371931345262199
>>> cls_b.__hash__()
-9223371931345260835
>>> id(cls_a)
1688152217752
>>> id(cls_b)
1688152239576
# 這里兩個對象(cls_a和cls_b)哈希值和id都不一樣
# 由於用戶自定義的類的實例其哈希值與id有關,所以id值和哈希值都不相同,就如官方文檔里說的,實例只跟自己相等。
Python內置的可哈希對象可以使用hash()
或者__hash__()
方法來查看它的哈希值,如:a.__hash__()
或者hash(a)
,而id()
函數用於獲取對象的內存地址。
后續思考
使用key-value
存儲結構的dict
在Python中非常有用,選擇不可變對象
作為key
很重要,最常用的key
是字符串
。
tuple
雖然是不可變對象
,但試試把(1, 2, 3)
和(1, [2, 3])
放入dict
或set
中
# tuple是不變對象,試試把(1, 2, 3)和(1, [2, 3])放入dict或set中
T = (1, 2, 3)
T2 = (1, [2, 3])
D = {T: 100}
print('D:', D)
'''----------------- D: {(1, 2, 3): 100} -----------------'''
# D2 = {T2: 100} # TypeError: unhashable type: 'list'
# print('D2:', D2)
S = set([T])
print('S:', S)
'''----------------- S: {(1, 2, 3)} -----------------'''
# S = set([T2]) # TypeError: unhashable type: 'list'
# print('S:', S)
參考文章
- 聊一聊Python中的hashable和immutable
- python-對象之hashable&unhashable與immutable&mutable
- What do you mean by hashable in Python?