新浪新闻

面试京东,卡壳了。。

新浪人工智能

关注

哈喽,我是cos大壮!~

今天知识星球中的每日一题:Python 中的深拷贝和浅拷贝区别?

很多同学反映,知道 赋值号(=),也知道深拷贝,但是对于浅拷贝的理解,之前真的没有怎么想过。

其实这个题目,是在2年前,有一位实习生面试京东的时候,遇到的问题,结果还真是卡住了。

我今天把他放在这里,确实有不少人有点懵。

最近知识星球中,大家的积极性越来越高,学习氛围也越来越好~

下面,分享一位夜友,在知识星球中的回答,大家可以当做学习~

三天没答题了,坚持看了壮哥这几天发布内容,着实学习了一番...

聊聊深拷贝和浅拷贝~

对象的拷贝,编码的时候,我们常常会碰到这种需求:我想复制一个对象,但又不想影响原对象。

听起来挺合理。但实际操作起来,复制这个动作,有时候很容易出问题。尤其当对象里嵌套了其他对象,事情就更复杂了。

首先,拷贝对象的方式有哪些?

简单说,就三种:

  • 使用=(赋值语句)

  • 使用copy.copy()(浅拷贝)

  • 使用copy.deepcopy()(深拷贝)

1. 赋值(=)不是拷贝!

这个容易被误会:

a = [1, 2, 3]
b = a

最开始学习的时候,我以为ba的副本,其实错了!它们指向同一个对象。换句话说,是两个变量共享同一个引用,修改其中一个,另一个也会变。

2. 浅拷贝

浅拷贝会创建一个新的对象,但内部的引用仍然指向原始对象中的子对象

import copy

a = [[1, 2], [3, 4]]
b = copy.copy(a)

这里ab是两个不同的列表对象(外层的list不一样了),但是它们里面的子列表a[0]b[0]同一个对象

3. 深拷贝

深拷贝就“干净”多了,它会递归地拷贝所有子对象,直到最底层,每一个子对象都是全新的副本

c = copy.deepcopy(a)

此时,ca完全独立。你对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)

此时所有内容都拷贝了一份,连scoresinfo也完全复制出来了。无论改哪里,原始数据都不会变。

浅拷贝它的开销更小,更快,适合结构不深或内部对象不需要隔离的场景。例如:

names = ["Alice", "Bob", "Charlie"]
copied_names = copy.copy(names)

这种情况下你不需要深拷贝,因为列表中放的是不可变的字符串。

几点注意事项

  1. 深拷贝慢,开销大,因为它要递归地拷贝每个子对象,特别是当对象结构很大、层级很深时,性能影响不容忽视。

  2. 深拷贝不适用于所有对象,如果对象中包含了文件句柄、数据库连接、线程对象、锁等非拷贝性资源,deepcopy()可能会出错或拷贝失败。

  3. 可能触发无限递归,在存在循环引用的对象中,例如:

a = []
a.append(a)
copy.deepcopy(a)  # 能正常工作,但背后做了大量处理

Python 的deepcopy()其实内部维护了一个 memo 表,来处理这种循环引用,防止死循环。

总结一下

其实,无论是浅拷贝还是深拷贝,我们要尽可能理解它们的工作原理,这也是学习语法的关键所在。尤其很多时候,我们的数据结构复杂、逻辑嵌套深时,如果是一个错误的引用共享,会很花费时间去修正,并且找出问题所在。

加载中...