Python學習筆記 | 關於python數據對象 hashable & unhashable 的理解


寫在前面

Hash(哈希、散列)是一個將大致量數據轉化爲很小數據的過程,甚至能夠僅僅是一個數字,以便咱們能夠在O(1)的時間複雜度下查詢它,因此,哈希對高效的算法和數據結構很重要。python

immutable(不可改變性)是指一些對象在被建立以後不會由於某些方式改變,特別是針對任何能夠改變哈希對象的哈希值的方式。web

因爲hash key必須是不可變(immutable)的,對應的hash value才能是不變,因此不可變(immutable)和可哈希(hashable)是有關係的。若是hash key容許改變,那麼像hashtable這樣數據結構的對象將會改變,整個hash映射就都會失效。算法

具體以例來看,元組(tuple)對象是不可變的(immutable),字典(dict)的鍵(key)必須是能夠哈希的(hashable)。數據結構

hashable & unhashable

官方文檔:
svg

若是一個對象在其生命週期內有一個固定不變的哈希值 (這須要__hash__()方法) 且能夠與其餘對象進行比較操做 (這須要__eq__()__cmp__()方法) ,那麼這個對象就是可哈希對象 (hashable) 。可哈希對象必須有相同的哈希值纔算做相等。函數

因爲字典 (dict) 的鍵 (key) 和集合 (set) 元素使用到了哈希值,因此只有可哈希 (hashable) 對象才能被用做字典的鍵和集合的元素。ui

全部python內置的不可變(immutable)對象(tuple等)都是可哈希的,同時,可變容器 (好比:列表 (list) 或者字典 (dict) ) 都是不可哈希的。用戶自定義的類的實例默認狀況下都是可哈希的;它們跟其它對象都不相等 (除了它們本身) ,它們的哈希值來自id()方法。spa

mutable & immutable

(轉自 https://www.jianshu.com/p/49f940b2c03e).net

首先要明白的是當咱們在聊可變與不可變對象時,咱們聊的是Python的內置對象。本身定義的對象一般咱們不去討論它是否是可變的,畢竟Python自己是一門動態語言,須要的話咱們隨時能夠給本身定義的這個對象添加其它的屬性和方法。code

提到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])放入dictset

# 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)

參考文章

相關文章
相關標籤/搜索