面试京东,卡壳了。。
新浪人工智能
哈喽,我是cos大壮!~
今天知识星球中的每日一题:Python 中的深拷贝和浅拷贝区别?
很多同学反映,知道 赋值号(=),也知道深拷贝,但是对于浅拷贝的理解,之前真的没有怎么想过。
其实这个题目,是在2年前,有一位实习生面试京东的时候,遇到的问题,结果还真是卡住了。
我今天把他放在这里,确实有不少人有点懵。
最近知识星球中,大家的积极性越来越高,学习氛围也越来越好~
下面,分享一位夜友,在知识星球中的回答,大家可以当做学习~
三天没答题了,坚持看了壮哥这几天发布内容,着实学习了一番...
聊聊深拷贝和浅拷贝~
对象的拷贝,编码的时候,我们常常会碰到这种需求:我想复制一个对象,但又不想影响原对象。
听起来挺合理。但实际操作起来,复制这个动作,有时候很容易出问题。尤其当对象里嵌套了其他对象,事情就更复杂了。
首先,拷贝对象的方式有哪些?
简单说,就三种:
使用=(赋值语句)
使用copy.copy()(浅拷贝)
使用copy.deepcopy()(深拷贝)
1. 赋值(=)不是拷贝!
这个容易被误会:
a = [1, 2, 3]
b = a
最开始学习的时候,我以为b是a的副本,其实错了!它们指向同一个对象。换句话说,是两个变量共享同一个引用,修改其中一个,另一个也会变。
2. 浅拷贝
浅拷贝会创建一个新的对象,但内部的引用仍然指向原始对象中的子对象。
import copy
a = [[1, 2], [3, 4]]
b = copy.copy(a)
这里a和b是两个不同的列表对象(外层的list不一样了),但是它们里面的子列表a[0]和b[0]是同一个对象!
3. 深拷贝
深拷贝就“干净”多了,它会递归地拷贝所有子对象,直到最底层,每一个子对象都是全新的副本。
c = copy.deepcopy(a)
此时,c和a完全独立。你对c里的任何改动,都不会影响到a。
再来深入分析浅拷贝 vs 深拷贝
这里是一个嵌套结构,看看两者的本质区别。
original = {
"name": "Alice",
"scores": [95, 88, 76],
"info": {
"age": 20,
"gender": "female"
}
}
这个字典结构中包含了:
一个字符串(不可变类型)
一个列表(可变类型)
一个嵌套的字典(可变类型)
浅拷贝行为
import copy
shallow = copy.copy(original)
这一步创建了一个新字典shallow,但:
shallow["name"]和original["name"]指向同一个字符串(没关系,因为字符串不可变)
shallow["scores"]和original["scores"]指向同一个列表
shallow["info"]和original["info"]指向同一个字典
也就是说,修改shallow["scores"][0]会影响原始对象。
深拷贝行为
deep = copy.deepcopy(original)
此时所有内容都拷贝了一份,连scores和info也完全复制出来了。无论改哪里,原始数据都不会变。
浅拷贝它的开销更小,更快,适合结构不深或内部对象不需要隔离的场景。例如:
names = ["Alice", "Bob", "Charlie"]
copied_names = copy.copy(names)
这种情况下你不需要深拷贝,因为列表中放的是不可变的字符串。
几点注意事项
深拷贝慢,开销大,因为它要递归地拷贝每个子对象,特别是当对象结构很大、层级很深时,性能影响不容忽视。
深拷贝不适用于所有对象,如果对象中包含了文件句柄、数据库连接、线程对象、锁等非拷贝性资源,deepcopy()可能会出错或拷贝失败。
可能触发无限递归,在存在循环引用的对象中,例如:
a = []
a.append(a)
copy.deepcopy(a) # 能正常工作,但背后做了大量处理
Python 的deepcopy()其实内部维护了一个 memo 表,来处理这种循环引用,防止死循环。
总结一下
其实,无论是浅拷贝还是深拷贝,我们要尽可能理解它们的工作原理,这也是学习语法的关键所在。尤其很多时候,我们的数据结构复杂、逻辑嵌套深时,如果是一个错误的引用共享,会很花费时间去修正,并且找出问题所在。