深入set和dict

一. 浅拷贝和深拷贝
 
浅拷贝:就是创建一个具有相同类型,相同值但不同id的新对象。 
浅拷贝产生的新对象中可变对象的值在发生改变时,会对原对象的值也做出改变,因为这些值是同一个引用。
a = [1, 2]
b = [3, 4, a]
c = b.copy()
a[0] = 0
print(b)
print(c)
输出
[3, 4, [0, 2]]
[3, 4, [0, 2]]
 
 
深拷贝:不仅仅拷贝了原始对象自身,也对其包含的值进行拷贝,它会递归的查找对象中包含的其他对象的引用,来完成更深层次拷贝。
深拷贝产生的副本可以随意修改而不需要担心会引起原始值的改变
import copy
 
a = [1, 2]
b = [3, 4, a]
c = copy.deepcopy(b)
a[0] = 0
print(b)
print(c)
输出结果
[3, 4, [0, 2]]
[3, 4, [1, 2]]
 
 
 
 
 
二. dict的常用方法
 
1. fromkeys把可迭代的对象转变为dict
基本语法:dict.fromkeys(可迭代对象作为keys,默认值作为value)
 
new_list = ["name1", "name2"]
new_dict = dict.fromkeys(new_list, 34)
print(new_dict)
输出如下
{'name1': 34, 'name2': 34}
 
 
2. clear,清空字典中数据
a = {"name1": "jack", "name2": "jane"}
print(a)
a.clear()
print(a)
输出如下
{'name2': 'jane', 'name1': 'jack'}
{}
 
 
3. copy(这里是浅拷贝)
a = {"name1": "jack", "name2": "jane"}
print(a)
b = a.copy()
print(b)
 
# 修改a的值后,b也会跟着改变;同样如果修改b,a也会相应改变
a["name1"] = "hong"
print(a)
b = a.copy()
print(b)
输出如下
{'name2': 'jane', 'name1': 'jack'}
{'name2': 'jane', 'name1': 'jack'}
{'name2': 'jane', 'name1': 'hong'}
{'name2': 'jane', 'name1': 'hong'}
 
 
4. get,避免字典中keyerror异常
语法:a.get(键名,默认值)如果字典a中没有某个键名,就返回默认值
a = {"name1": "jack", "name2": "jane"}
value = a.get("name3", "not exist")
print(value)
输出如下
not exist
 
 
5. setdefault(),和get()相似,不同的是如果没有某个键名,会把此键名和默认值加入到字典中
a = {"name1": "jack", "name2": "jane"}
 
# 没有指定键名时
a.setdefault("name3", "not exist")
print(a)
 
# 存在此键名时
b=setdefault("name1", "not exist")
print(b)
输出如下
{'name2': 'jane', 'name3': 'not exist', 'name1': 'jack'}
jack
 
 
6. update(), 可用于添加字典元素
a = {"name1": "jack", "name2": "jane"}
 
# 直接添加字典方式
a.update({"name3": "hong"})
print(a)
 
# 使用参数名方式
a.update(name4="lilei", name5="mei")
print(a)
 
# 使用list嵌套tuple方式,同样的tuple也可以嵌套tuple
a.update([("name6", "tiger"), ("name7", "monkey")])
print(a)
输出如下
{'name2': 'jane', 'name1': 'jack', 'name3': 'hong'}
{'name2': 'jane', 'name1': 'jack', 'name4': 'lilei', 'name5': 'mei', 'name3': 'hong'}
{'name6': 'tiger', 'name1': 'jack', 'name4': 'lilei', 'name3': 'hong', 'name7': 'monkey', 'name2': 'jane', 'name5': 'mei'}
 
 
 
 
 
三. set和frozenset(不可变集合)
 
特点:元素不会重复,无序性,里面的对象需要是可迭代,使用hash方式能很高
frosenset无法使用add函数添加值
 
1. 初始化方法
# 使用set关键字
s1 = set('abc')
 
#使用{}
s2 = {'a', 'b'}
 
print(type(s1), type(s2))
输出
<class 'set'> <class 'set'>
 
2. 添加元素方式
# 使用add()函数
s1 = set('abc')
s1.add('d')
print(s1)  
 
# 使用update()函数
s2 = set('xy')
s1.update(s2)
print(s1)
输出
{'a', 'c', 'd', 'b'}
{'b', 'a', 'x', 'c', 'y', 'd'}
 
3. difference()函数来求两个集合的差集
s1 = set('abc')
s2 = set('cd')
 
# 相当于s1 - s2
re_set = s1.difference(s2)
print(re_set)
输出结果如下
{'b', 'a'}
注意:difference()函数会返回一个新值,不会修改原数据
 
4.  集合的并集,交集
s1 = set('abc')
s2 = set('cd')
# 交集
re_set1 = s1 & s2
# 并集
re_set2 = s1 | s2
print(re_set1, re_set2)
输出结果如下
{'c'} {'c', 'a', 'b', 'd'}
 
5. issubset()判断集合A是否为集合B的子集
s1 = set('abc')
s2 = set('c')
 
print(s2.issubset(s1))
输出结果为True
 
 
 
 
 
 
四. dict和set的内部实现原理
 
1. list和dict的性能比较
1)dict的查找性能远大于list
2)在list中随着list数据量的增大,查找相同数据量的时间也会增大
3)在dict中随着dict数据量的增大,查找相同数据量的时间不怎么受影响
 
 
 
2. hash表数据存储实现图
 
 
1)右侧是hash表的存储结构,是一个数组,使用了一段连续的内存空间
2)字典中的key进行hash计算得到一个hash值,然后和7进行与的位运算后,得到一个值,比如是0,就把dict中的键值对放到0的这个内存位置
3)不同的key进行hash计算后,可能会得到一个相同的hash值,从而造成冲突;为解决这个问题,可按照一种机制重新进行hash计算。
4)声明数组的时候,分配的内存空间会比实际的dict数据量大
5)只须根据dict的key找到数组中的前面的偏移量,即可得到相应的键值对,复杂度为O(1)
 
 
3. 哈希表查找原理图
 
说明:
1)表元就是哈希图中最右侧存储键值对的地方
2)散列冲突就是hash冲突
3)造成散列冲突时,会多取几位散列值来定位表元。
 
 
4. dict原理小结
1)dict的key或者set的值都必须是可hash的,他们的实现原理相同。不可变对象都是可hash的,比如string, tuple, fronzenset
2)dict的内存花销大,但是查询速度快;自定义的类中,只要加上魔法函数__hash__, 那么这个类就是可hash的
3)dict的存储顺序和元素添加顺序有关,因hash值冲突的原因
4)添加数据的时候有可能会改变已有数据的顺序:存储dict的时候,python会预先申请一段大于dict数据需求的连续内存空间,以减少hash冲突的概率,当添加数据量使得大于分配内存空间的1/3的时候,python就会另申请一个较大的内存空间,把原先的数据进行迁移,重新进行hash值的计算
 
 
posted @ 2018-10-31 09:37  坚强的小蚂蚁  阅读(300)  评论(0编辑  收藏  举报