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)

参考文章

相关文章
相关标签/搜索