python字典与hashable的关系
最近在写代码的时候发现了一个问题:我有一系列的元组(实际上是强化学习的state和action),它们要作为字典的键,而值是reward,我以前一直认为只有字符串才能作为键,然后就用str()函数将元组转化成string类型,后来要画图,又要得到字典的键,得到的是带有元组括号的一些杂七杂八的东西。。。
后来才想起来不可变类型都是可散列的,都可以作为键。。。
然后就好好的写一下这个可hash背后原因,因为数据结构散列寻址法忘了好多。
python是动态类型语言,它在初始变量时不需要声明类型。还有一个特点:在Python中变量存储的机制是完全不一样的,当给一个变量赋值时首先解释器会给这个值分配内存空间,然后将变量指向这个值的地址,那么当我们改变变量值的时候解释器又会给新的值分配另一个内存空间,再将变量指向这个新值的地址,所以和C语言相比,在Python中改变的是变量所指向的地址,而内存空间中的值是固定不变的。
对于不可变类型,如整数,字符串,元组等,如果两个变量值相同,那么这2个变量指向相同内存;如下
i = 5
print "i ---> ",i
print "id(i) ---> ",hex(id(i))
j = 5
print "j ---> ",j
print "id(j) ---> ",hex(id(j))
结果:
i ---> 5
id(i) ---> 0xa26f880
j ---> 5
id(i) ---> 0xa26f880
可以看到i和j指向相同的地址
然而当在i上进行操作时,地址会变化,相当于先计算生成这个新的数值,并为该值分配新的内存,然后刚才的i会指向这个新的值,所以内存发生变化,而前面那个引用会根据引用计数机制或者其他gc机制自动回收:如下
i += 1
print "i ---> ",i
print "id(i) ---> ",hex(id(i))
结果:
i ---> 6
id(i) ---> 0xa26f874
原来是: 0xa26f880
重点来了,以上仅仅是对于不可变类型,而对于可变类型呢?
如列表:
i = [1, 2, 3]
print "i ---> ",i
print "id(i) ---> ",hex(id(i))
i.append(4)
print "i ---> ",i
print "id(i) ---> ",hex(id(i))
j = [1.5, 2.5, 3.5]
print "j ---> ",j
print "id(j) ---> ",hex(id(j))
k = [1.5, 2.5, 3.5]
print "k ---> ",j
print "id(k) ---> ",hex(id(k))
结果:
i ---> [1, 2, 3]
id(i) ---> 0xb73fa1acL
i ---> [1, 2, 3, 4]
id(i) ---> 0xb73fa1acL
j ---> [1.5, 2.5, 3.5]
id(j) ---> 0xb6fed06cL
k ---> [1.5, 2.5, 3.5]
id(k) ---> 0xb6fed04cL
可以看到一个列表append后,id是不变的,***而如果两个列表元素相同,则并不是指向同一地址***这是可hash与否的关键!!!
第一,字典的本质是散列寻址,而散列寻址的条件是相同数值的元素通过映射地址必须相同(否则不满足函数的定义,不能一对多)
第二,相应的字典有一个性质,键名不得重复,所以对于不可变类型,如x = 2和y = 2本身就指向相同的地址,如果作为字典键的话,自然也不会引起重复键名;
而可变类型,如x = [1, 2, 3, 4], y = [1, 2, 3, 4], 这两个指向了不同的地址,所以没办法作为键名啊,如果作为键名 {[1, 2, 3, 4]:…},该键名到底是映射成x的地址呢,还是y的地址呢,这是不确定的,也不满足散列函数定义;而不可变类型(可hash的)只要内容相同,则映射成相同的地址,这是确定的,也满足函数的定义。
具体的可散列的必须要满足哪些条件可参考《流畅的python》
哈希这个名字第一次是在perl里听说的,perl中的哈希在python中叫字典。。。