Skip to content

深浅拷贝 ​

前两节一直在操作学生列表。现在有一个新需求:修改一批学生数据,同时保留修改前的内容作为备份,怎样复制才能让备份真正独立?

案例:备份学生数据 ​

需求:修改 backup 中的学生信息时,原来的 students 不应被意外改变。先看最直接的写法:

python
students = [{"name": "张三", "chinese": 88, "math": 92}]
backup = students

这段代码并没有真正复制数据。修改 backup 时,students 也可能跟着变化。

接下来对比三种写法:普通赋值、浅拷贝和深拷贝。

对象引用、浅拷贝与深拷贝机制总览

对象引用与普通赋值 ​

要解释备份为什么会跟着变化,先看列表和字典在内存中的关系。它们创建后会作为对象保存,变量保存的是对象的引用。

可以先理解为:变量不是数据本身,而是用来找到数据的名字。

当两个变量指向同一个对象时,通过其中一个变量修改对象,另一个变量看到的内容也会变化。

backup = students 只把引用赋值给了新变量,没有创建新列表:

python
students = ["张三", "李四", "王五"]
backup = students

backup[0] = "赵六"

print(students)
print(backup)
print(students is backup)

运行结果:

text
['赵六', '李四', '王五']
['赵六', '李四', '王五']
True

is 用来判断两个变量是否指向同一个对象。结果为 True,说明 students 和 backup 只是同一个列表的两个名字。

变量绑定与对象引用内存模型

所以,普通赋值不适合用来备份可变数据。

浅拷贝 ​

如果只需要复制一层列表,浅拷贝可以创建新的外层对象。列表有三种常见写法:

python
students = ["张三", "李四", "王五"]

backup1 = students.copy()
backup2 = list(students)
backup3 = students[:]

students[0] = "赵六"

print(students)
print(backup1)
print(backup2)
print(backup3)

运行结果:

text
['赵六', '李四', '王五']
['张三', '李四', '王五']
['张三', '李四', '王五']
['张三', '李四', '王五']

这三种写法都会创建一个新的外层列表。修改 students 的第一个元素,不会改变三个备份列表。

也可以使用 copy 模块中的 copy():

python
import copy

students = ["张三", "李四", "王五"]
backup = copy.copy(students)

浅拷贝一维平铺语法与独立外层模型

嵌套对象共享 ​

学生信息管理系统使用“列表套字典”结构。问题是:浅拷贝后修改学生字典,原数据会不会一起变化?

python
students = [
    {"name": "张三", "chinese": 88, "math": 92},
]

backup = students.copy()
backup[0]["chinese"] = 100

print(students)
print(backup)

运行结果:

text
[{'name': '张三', 'chinese': 100, 'math': 92}]
[{'name': '张三', 'chinese': 100, 'math': 92}]

这次外层的 students 和 backup 是两个列表,但里面的学生字典仍然是共享的。

这就是浅拷贝:只复制外层对象,内部元素仍然沿用原来的引用。

浅拷贝嵌套指针穿透与数据污染陷阱

浅拷贝不是不能用 如果只需要调整外层容器,例如增加、删除或重新排列外层元素,浅拷贝通常就够了。问题主要出现在继续修改内部可变对象时。

深拷贝 ​

如果备份中的嵌套数据也要独立修改,就需要继续复制内部对象,使用 copy.deepcopy():

python
import copy

students = [
    {"name": "张三", "chinese": 88, "math": 92},
]

backup = copy.deepcopy(students)
backup[0]["chinese"] = 100

print(students)
print(backup)

运行结果:

text
[{'name': '张三', 'chinese': 88, 'math': 92}]
[{'name': '张三', 'chinese': 100, 'math': 92}]

deepcopy() 会继续复制内部的字典和列表。修改备份中的成绩,不会再影响原数据。

深拷贝递归克隆与完全内存隔离

复制方式选择 ​

面对不同的备份需求,先看数据有没有嵌套,再看是否需要修改嵌套内容:

写法外层对象嵌套的可变对象适合场景
backup = data共享共享需要让两个变量操作同一份数据
copy.copy(data)新建共享只需要独立调整外层容器
copy.deepcopy(data)新建继续复制嵌套数据也要独立修改
  1. 只想增加一个变量名,使用普通赋值
  2. 只需要复制外层容器,使用浅拷贝
  3. 嵌套内容也必须互不影响,使用深拷贝

深浅拷贝选型决策树与特性对照矩阵

深拷贝会复制更多内容,占用的时间和内存通常也更多,所以不需要遇到复制就直接使用 deepcopy()。

小结 ​

本节先回答三个问题:

  1. 为什么普通赋值不能作为独立备份?因为两个变量仍然共享同一个对象
  2. 浅拷贝能解决什么问题?它创建新的外层对象,但嵌套的可变对象仍然共享
  3. 什么时候使用深拷贝?嵌套内容也必须互不影响时

练习 ​

  1. 创建一个一维列表,分别使用赋值、copy() 和切片复制,修改后观察结果
  2. 创建一个“字典套列表”的数据,分别测试浅拷贝和深拷贝
  3. 对浅拷贝后的学生列表分别执行“新增学生”和“修改已有学生成绩”,观察哪一步会影响原数据