深浅拷贝
前两节一直在操作学生列表。现在有一个新需求:修改一批学生数据,同时保留修改前的内容作为备份,怎样复制才能让备份真正独立?
案例:备份学生数据
需求:修改 backup 中的学生信息时,原来的 students 不应被意外改变。先看最直接的写法:
students = [{"name": "张三", "chinese": 88, "math": 92}]
backup = students这段代码并没有真正复制数据。修改 backup 时,students 也可能跟着变化。
接下来对比三种写法:普通赋值、浅拷贝和深拷贝。
对象引用与普通赋值
要解释备份为什么会跟着变化,先看列表和字典在内存中的关系。它们创建后会作为对象保存,变量保存的是对象的引用。
可以先理解为:变量不是数据本身,而是用来找到数据的名字。
当两个变量指向同一个对象时,通过其中一个变量修改对象,另一个变量看到的内容也会变化。
backup = students 只把引用赋值给了新变量,没有创建新列表:
students = ["张三", "李四", "王五"]
backup = students
backup[0] = "赵六"
print(students)
print(backup)
print(students is backup)运行结果:
['赵六', '李四', '王五']
['赵六', '李四', '王五']
Trueis 用来判断两个变量是否指向同一个对象。结果为 True,说明 students 和 backup 只是同一个列表的两个名字。
所以,普通赋值不适合用来备份可变数据。
浅拷贝
如果只需要复制一层列表,浅拷贝可以创建新的外层对象。列表有三种常见写法:
students = ["张三", "李四", "王五"]
backup1 = students.copy()
backup2 = list(students)
backup3 = students[:]
students[0] = "赵六"
print(students)
print(backup1)
print(backup2)
print(backup3)运行结果:
['赵六', '李四', '王五']
['张三', '李四', '王五']
['张三', '李四', '王五']
['张三', '李四', '王五']这三种写法都会创建一个新的外层列表。修改 students 的第一个元素,不会改变三个备份列表。
也可以使用 copy 模块中的 copy():
import copy
students = ["张三", "李四", "王五"]
backup = copy.copy(students)嵌套对象共享
学生信息管理系统使用“列表套字典”结构。问题是:浅拷贝后修改学生字典,原数据会不会一起变化?
students = [
{"name": "张三", "chinese": 88, "math": 92},
]
backup = students.copy()
backup[0]["chinese"] = 100
print(students)
print(backup)运行结果:
[{'name': '张三', 'chinese': 100, 'math': 92}]
[{'name': '张三', 'chinese': 100, 'math': 92}]这次外层的 students 和 backup 是两个列表,但里面的学生字典仍然是共享的。
这就是浅拷贝:只复制外层对象,内部元素仍然沿用原来的引用。
浅拷贝不是不能用 如果只需要调整外层容器,例如增加、删除或重新排列外层元素,浅拷贝通常就够了。问题主要出现在继续修改内部可变对象时。
深拷贝
如果备份中的嵌套数据也要独立修改,就需要继续复制内部对象,使用 copy.deepcopy():
import copy
students = [
{"name": "张三", "chinese": 88, "math": 92},
]
backup = copy.deepcopy(students)
backup[0]["chinese"] = 100
print(students)
print(backup)运行结果:
[{'name': '张三', 'chinese': 88, 'math': 92}]
[{'name': '张三', 'chinese': 100, 'math': 92}]deepcopy() 会继续复制内部的字典和列表。修改备份中的成绩,不会再影响原数据。
复制方式选择
面对不同的备份需求,先看数据有没有嵌套,再看是否需要修改嵌套内容:
| 写法 | 外层对象 | 嵌套的可变对象 | 适合场景 |
|---|---|---|---|
backup = data | 共享 | 共享 | 需要让两个变量操作同一份数据 |
copy.copy(data) | 新建 | 共享 | 只需要独立调整外层容器 |
copy.deepcopy(data) | 新建 | 继续复制 | 嵌套数据也要独立修改 |
- 只想增加一个变量名,使用普通赋值
- 只需要复制外层容器,使用浅拷贝
- 嵌套内容也必须互不影响,使用深拷贝
深拷贝会复制更多内容,占用的时间和内存通常也更多,所以不需要遇到复制就直接使用 deepcopy()。
小结
本节先回答三个问题:
- 为什么普通赋值不能作为独立备份?因为两个变量仍然共享同一个对象
- 浅拷贝能解决什么问题?它创建新的外层对象,但嵌套的可变对象仍然共享
- 什么时候使用深拷贝?嵌套内容也必须互不影响时
练习
- 创建一个一维列表,分别使用赋值、
copy()和切片复制,修改后观察结果 - 创建一个“字典套列表”的数据,分别测试浅拷贝和深拷贝
- 对浅拷贝后的学生列表分别执行“新增学生”和“修改已有学生成绩”,观察哪一步会影响原数据