1:预备知识(Hash)
1.1:在Python3中,dict和set数据结构要求键值key是可hash的,这样可以保证键值key的唯一性。简要的说可hash的数据类型就是不可变的数据结构(字符串str、元组tuple、对象集objects),它是一个将大体量数据转化为很小数据的过程,甚至可以仅仅是一个数字,以便我们可以用在固定的时间复杂度下查询它,所以,hash对高效的算法和数据结构很重要。这点和数字图像处理中经常用到的LUT(look-up-table)很像,感兴趣的可以百度一下。
1.2:不可哈希的类型有列表(list可以append和pop进行变化)、字典(dict可以添加key与value变化)、集合(set()具有add等方法)
这里简单说一下,使用Hash算法的数据结构叫做哈希表,也叫散列表,主要是为了提高查询的效率。他通过映射来访问自己想要的值,而Python3中的字典就是一个典型的哈希表。
1.3:Python3中使用 __hash__ 返回一个int值,用来唯一标记这个对象。
class test:
def __init__(self,value):
self.val = value
p1 = test(1)
p2 = test(2)
p3 = test(3)
print(set([p1,p2,p3]))
In [49]:runfile('C:/Users/16288/Desktop/ALBB.py', wdir='C:/Users/16288/Desktop')
{<__main__.test object at 0x0000022563BB04A8>, <__main__.test object at 0x0000022563BB01D0>, <__main__.test object at 0x0000022563BB0160>}
可以看到test类自动继承了__hash__ 函数,输出了值。
1.4:什么是__eq__与__hash__?
在官方文档中,它们俩定义如下:
def __hash__(self):
return hash(id(self))
def __eq__(self, other):
if isinstance(other, self.__class__):
return hash(id(self))==hash(id(other))
else:
return False
首先考虑非类对象:
a = 2
b = 3
c = 4
d = 2
print(set([a,b,c,d]))
其结果为:
{2, 3, 4}
查看他们的hash,可以看到,其中a和d因为值一样,所以hash后的值也是一样的。
那么对于类对象呢?
做一个比喻,譬如你有1~N号盆,每个盆只能放一类球,又有m个类型的小球要放到盆里面。而__hash__作用就是判断第i个小球可以放到哪个盆里面,找到盆的文章,__eq__的作用就是在已经找到了盆的情况下,若这个盆已经有了一个小球,但又来了一个球,它声称它也应该装进这个盆里面(__hash__函数给它说了盆的位置),双方僵持不下,那就得用__eq__函数来判断这两个球是不是相等的(equal),如果是判断是相等的,那么后来那个球就不应该放进盆里,哈希集合维持现状。
class Foo:
def __init__(self,name,count):
self.name = name
self.count = count
def __hash__(self):
print("%s调用了哈希方法"%self.name)
return hash(self.count)
def __eq__(self, other):
print("%s调用了eq方法"%self.name)
return self.__dict__==other.__dict__
f1 = Foo('f1',1)
f2 = Foo('f2',2)
f3 = Foo('f3',3)
ls = set([f1,f2,f3])
print(ls)
print('===================')
f4 = Foo('f4',3)
ls.add(f4)
print('f3的id:',id(f3))
print('f4的id:',id(f4))
结果如下:
其中count是key值,也就是需要hash的值,而name和count会在后面eq方法中的__dict__出现,可以看到,f3和f4的hash值是相等的,但是set并没有这么简单就判断f4,f3是重复的,而是继续调用了eq方法判断f3和f4是否相等,只有相等的时候才会认为是同一个。
为了验证上面的假设,我们可以执行以下输入:
f1 = Foo('f1',1)
f2 = Foo('f1',1)
f3 = Foo('f3',3)
ls = set([f1,f2,f3])
print(ls)
结果为:
很明显,这里只存了两个值,所以不难看出,f1和f2被set判断重复,只取了一个。
2:结论
set的去重是通过两个函数__hash__和__eq__结合实现的。
1、当两个变量的哈希值不相同时,就认为这两个变量是不同的
2、当两个变量哈希值一样时,调用__eq__方法,当返回值为True时认为这两个变量是同一个,应该去除一个。返回FALSE时,不去重
而dict直接采用的是hashmap的结构。
PS:实际上set可以看作是没有value的dict,请读者自行思考原因。