前言
常常在传参, 赋值, 拷贝的时候进入迷乱的状态, 这个xxx之前是这样, 怎么xxx之后变了, 或者, 为什么这个xxx之后还是没有变啊~! (比如遇到类似于下面的题目).于是, 一次次的来回检查代码, 嗯,这代码块没有错, 这也没有错..., 抓狂...表情不好了, 内心纠结不安, 有点想摔鼠标和掀桌子的冲动. 这位少侠, 请等等~~~


# list1 , list2 , list3 分别是多少 def extendList(val, list=[]): list.append(val) return list list1 = extendList(1) print(list1) # [1] list2 = extendList(123, []) print(list2) # [123] list3 = extendList('a') print(list3) # [1, "a"] # 原因: list1 处的调用 与 list3 处的调用都共用了默认的参数 list=[] 的引用 # 避免: 使用不可变数据类型, 如 tuple 等
进入正题, 在开始之前, 我们得先了解一下, Python的数据类型分类, 分为可变数据(也称不可哈希 unhashable)和不可变数据(也称可哈希 hashable).
不可变数据类型 | bool, int, float, str, tuple, complex |
可变数据类型 | list, dict, set |
一. 对于不可变数据类型的深浅拷贝
1. 无论怎么操作,你又能奈我何
import copy def func(f: int): return f a = 10 print('原本,', a, id(a)) f = func(a) print('传参,', f, id(f)) b = a print('赋值,', b, id(b)) c = copy.copy(a) print('浅拷贝,', c, id(c)) d = copy.deepcopy(a) print('深拷贝,', d, id(d))
执行结果
原本, 10 1832873264 传参, 10 1832873264 赋值, 10 1832873264 浅拷贝, 10 1832873264 深拷贝, 10 1832873264
从执行的结果得知,对于不可变数据类型,以上的操作都是相当于贴标签,另起别名,但还是使用同一个地址内容。
2. 且慢,改变原变量的值先
import copy def func(f: int): return f a = 10 # print('原本,', a, id(a)) f = func(a) # print('传参,', f, id(f)) b = a # print('赋值,', b, id(b)) c = copy.copy(a) # print('浅拷贝,', c, id(c)) d = copy.deepcopy(a) # print('深拷贝,', d, id(d)) a = 100 print('改变,', a, id(a)) print('传参,', f, id(f)) print('赋值,', b, id(b)) print('浅拷贝,', c, id(c)) print('深拷贝,', d, id(d))
执行结果:
改变, 100 1832876144 传参, 10 1832873264 赋值, 10 1832873264 浅拷贝, 10 1832873264 深拷贝, 10 1832873264
从执行的结果得知,对于不可变的数据类型,原变量的改变,对其他的变量不产生影响。
这种情况,就像大家都来到了同一个房间里,而这个房间存着一个值,同时这个房间可以容纳无数个人,只要来到这个房间就可以拿到这个值。而以上的情况是,a,b,c,d,f都来到了同一个房间,但最后 a 离开了,去了其他的房间,而其他的人还在这个房间里,因此 a 拿到的值和其他人拿到的值不同。
二. 对于可变数据类型的深浅拷贝
对于不可变的数据类型,可以不分什么深浅拷贝,但对于可变的数据类型,总该区别一下了吧
好,那我们来了解一下什么是浅拷贝,什么是深拷贝
浅拷贝 | 将原对象的引用直接赋值给新对象 | 特点:只拷贝一级的元素 |
深拷贝 | 将原对象的值拷贝给新对象 | 特点:原对象做任何修改都不会改变新对象 |
1. 一波操作之后,且看结果如何
import copy def func(lst: list): return lst list_a = [1, 2, [3, 4]] print('原本,', list_a, [id(x) for x in list_a]) list_b = list_a print('赋值,', list_b, [id(x) for x in list_b]) list_c = copy.copy(list_a) print('浅拷贝,', list_c, [id(x) for x in list_c]) list_d = copy.deepcopy(list_a) print('深拷贝,', list_d, [id(x) for x in list_d]) list_f = func(list_a) print('传参,', list_f, [id(x) for x in list_f])
执行结果:
原本, [1, 2, [3, 4]] [1832872976, 1832873008, 2249777909128] 赋值, [1, 2, [3, 4]] [1832872976, 1832873008, 2249777909128] 浅拷贝, [1, 2, [3, 4]] [1832872976, 1832873008, 2249777909128] 深拷贝, [1, 2, [3, 4]] [1832872976, 1832873008, 2249778057608] 传参, [1, 2, [3, 4]] [1832872976, 1832873008, 2249777909128]
从执行的结果来看,得到的值都一样,但是,深拷贝奇葩,最后的一个地址不同。那么深拷贝到底去做了什么不为人知的事情?
他另外去申请其他的房间(地址),然后将那个房间改造为和原来房间的结构一模一样的房间(值相同)
2. 你把原对象的值改改,让我看看是不是和不可变的数据类型一样
import copy def func(lst: list): return lst list_a = [1, 2, [3, 4]] # print('原本,', list_a, [id(x) for x in list_a]) list_b = list_a # print('赋值,', list_b, [id(x) for x in list_b]) list_c = copy.copy(list_a) # print('浅拷贝,', list_c, [id(x) for x in list_c]) list_d = copy.deepcopy(list_a) # print('深拷贝,', list_d, [id(x) for x in list_d]) list_f = func(list_a) # print('传参,', list_f, [id(x) for x in list_f]) list_a[0] = 5 list_a[2][0] = 6 print('改变,', list_a, [id(x) for x in list_a]) print('赋值,', list_b, [id(x) for x in list_b]) print('浅拷贝,', list_c, [id(x) for x in list_c]) print('深拷贝,', list_d, [id(x) for x in list_d]) print('传参,', list_f, [id(x) for x in list_f])
走......
改变, [5, 2, [6, 4]] [1832873104, 1832873008, 2975057022280] 赋值, [5, 2, [6, 4]] [1832873104, 1832873008, 2975057022280] 浅拷贝, [1, 2, [6, 4]] [1832872976, 1832873008, 2975057022280] 深拷贝, [1, 2, [3, 4]] [1832872976, 1832873008, 2975056935880] 传参, [5, 2, [6, 4]] [1832873104, 1832873008, 2975057022280]
唉...这一波变化还挺大的哈, 那就来看到底是怎么了
(1) 首先, 赋值操作, 会跟随着原对象的值的改变而改变, 完全跟着改变那种
(2) 传参和赋值一样, 一类人
(3) 浅拷贝操作, 不跟着你改变而改变, 但是那些深层次的我可管不了, 因为我拿到的是一个对象, 而不是元素本身
(4) 深拷贝操作, 雷打不动, 不动如山, 我花费那么多的功夫来打造属于自己的地盘, 你说你换你的地盘跟我有一毛钱关系吗
总结:
1. 传参传的是引用.
2. 对于不可变数据类型, 不分深浅拷贝.
3. 对于可变数据类型, 浅拷贝只拷贝一级的元素, 其他层级的对象里的元素会随着原对象的同等层级对象的改变而改变; 深拷贝则无论原对象做任何修改, 都不受到影响.