深入理解 Python 中的深浅拷贝(Shallow Copy & Deep Copy):避免数据引用的 “坑”

引言: 在 Python 编程中,赋值、浅拷贝、深拷贝是处理数据复制时的核心操作 ------ 很多新手会因混淆三者的区别,导致数据被意外修改,引发难以排查的 bug。本文聚焦深浅拷贝这一核心知识点,从底层原理、使用场景到实战避坑,带你彻底掌握不同拷贝方式的本质,精准控制数据的复制逻辑。

一、深浅拷贝的核心背景:Python 的对象引用机制

要理解深浅拷贝,首先要明确 Python 的核心规则:变量存储的不是数据本身,而是数据对象的引用(内存地址)

  • 不可变对象(int、str、tuple):修改时会创建新对象,原引用指向新地址;
  • 可变对象(list、dict、set):修改时不会创建新对象,而是直接修改原地址的数据,所有指向该地址的变量都会受影响。
python 复制代码
# 示例:可变对象的引用特性
a = [1, 2, 3]
b = a  # 赋值:b和a指向同一个列表对象
b.append(4)
print(a)  # 输出:[1,2,3,4](a也被修改)
print(id(a) == id(b))  # 输出:True(内存地址相同)

赋值仅传递引用,而非复制数据 ------ 这也是深浅拷贝存在的核心原因:当需要 "真正复制" 数据,而非共享引用时,就需要用到拷贝操作。

二、浅拷贝(Shallow Copy):仅复制表层结构

1. 浅拷贝的定义

浅拷贝会创建一个新的容器对象,但容器内的元素依然引用原对象的元素(即只拷贝 "第一层",深层元素仍共享引用)。

2. 浅拷贝的实现方式

python 复制代码
import copy

# 原数据:嵌套可变对象(列表包含列表)
original = [1, [2, 3], 4]

# 方式1:使用copy模块的copy()函数(推荐)
shallow1 = copy.copy(original)
# 方式2:列表切片(仅适用于序列类型)
shallow2 = original[:]
# 方式3:对象的copy方法(如list.copy())
shallow3 = original.copy()

# 验证:新对象与原对象地址不同(表层已拷贝)
print(id(original) != id(shallow1))  # 输出:True

# 验证:深层元素仍共享引用
shallow1[1].append(5)
print(original[1])  # 输出:[2,3,5](原数据的深层列表被修改)
print(shallow1[1] == original[1])   # 输出:True(地址相同)

3. 浅拷贝的适用场景

  • 数据为单层结构 的可变对象(如 [1,2,3]{"a":1});
  • 无需修改深层元素,仅需独立操作表层结构的场景;
  • 追求拷贝效率(浅拷贝比深拷贝更快,占用内存更少)。

三、深拷贝(Deep Copy):完全复制所有层级

1. 深拷贝的定义

深拷贝会创建一个全新的独立对象,递归复制原对象的所有层级元素 ------ 新对象与原对象完全隔离,修改任意层级的元素都不会影响对方。

2. 深拷贝的实现方式

python 复制代码
import copy

# 原数据:嵌套可变对象
original = [1, [2, 3], 4]

# 使用copy模块的deepcopy()函数
deep = copy.deepcopy(original)

# 验证:新对象与原对象地址不同
print(id(original) != id(deep))  # 输出:True

# 验证:深层元素也完全独立
deep[1].append(5)
print(original[1])  # 输出:[2,3](原数据不受影响)
print(deep[1])      # 输出:[2,3,5](仅深拷贝对象被修改)
print(id(original[1]) != id(deep[1]))  # 输出:True(深层地址不同)

3. 深拷贝的适用场景

  • 数据包含嵌套可变对象 (如 [[1,2], [3,4]]{"info": {"age": 20}});
  • 需要完全独立操作数据,避免修改相互影响的场景;
  • 数据持久化、数据备份等需要 "纯净副本" 的场景。

四、赋值、浅拷贝、深拷贝对比

为了更清晰区分三者,我们用表格总结核心差异:

操作方式 是否创建新对象 表层元素是否独立 深层元素是否独立 适用场景
赋值 ❌ 否 ❌ 共享 ❌ 共享 仅需引用数据,无需独立操作
浅拷贝 ✅ 是 ✅ 独立 ❌ 共享 单层可变对象、追求拷贝效率
深拷贝 ✅ 是 ✅ 独立 ✅ 独立 嵌套可变对象、需完全隔离数据

实战对比示例

python 复制代码
import copy

# 原数据
data = {"name": "Alice", "scores": [80, 90]}

# 1. 赋值
assign = data
assign["scores"].append(95)
print(data["scores"])  # [80,90,95](原数据被改)

# 重置原数据
data = {"name": "Alice", "scores": [80, 90]}

# 2. 浅拷贝
shallow = copy.copy(data)
shallow["scores"].append(95)
print(data["scores"])  # [80,90,95](深层仍被改)
shallow["name"] = "Bob"
print(data["name"])    # Alice(表层独立)

# 重置原数据
data = {"name": "Alice", "scores": [80, 90]}

# 3. 深拷贝
deep = copy.deepcopy(data)
deep["scores"].append(95)
deep["name"] = "Bob"
print(data["scores"])  # [80,90](完全不受影响)
print(data["name"])    # Alice

五、深浅拷贝的避坑要点

1. 不可变对象的拷贝 "特例"

对不可变对象(int、str、tuple)执行深浅拷贝,结果等同于赋值 ------ 因为不可变对象无法修改,Python 会优化为共享引用,避免不必要的内存消耗:

python 复制代码
import copy

a = (1, 2, 3)  # 不可变元组
b = copy.copy(a)
c = copy.deepcopy(a)
print(id(a) == id(b) == id(c))  # 输出:True

2. 循环引用的处理

深拷贝能自动处理循环引用(对象引用自身),而浅拷贝会保留循环引用,可能导致内存泄漏:

python 复制代码
import copy

# 循环引用:列表引用自身
lst = [1, 2]
lst.append(lst)

# 深拷贝:正确处理循环引用
deep_lst = copy.deepcopy(lst)
print(deep_lst[-1] is deep_lst)  # 输出:True(新的循环引用)

# 浅拷贝:保留原循环引用
shallow_lst = copy.copy(lst)
print(shallow_lst[-1] is lst)    # 输出:True(引用原列表)

3. 自定义对象的拷贝

自定义类的实例默认使用浅拷贝,若需深拷贝,需重写 __deepcopy__ 方法:

python 复制代码
import copy

class Person:
    def __init__(self, name, hobbies):
        self.name = name
        self.hobbies = hobbies  # 可变对象(列表)

# 自定义对象的浅拷贝
p1 = Person("Alice", ["reading", "running"])
p2 = copy.copy(p1)
p2.hobbies.append("swimming")
print(p1.hobbies)  # ['reading', 'running', 'swimming'](被修改)

# 自定义对象的深拷贝
p3 = copy.deepcopy(p1)
p3.hobbies.append("cooking")
print(p1.hobbies)  # ['reading', 'running', 'swimming'](不受影响)

总结

  1. Python 中变量存储的是对象引用,赋值仅传递引用,不会创建新对象;
  2. 浅拷贝(copy.copy())创建新的表层对象,深层元素仍共享引用,适用于单层可变对象;
  3. 深拷贝(copy.deepcopy())递归复制所有层级元素,新对象与原对象完全隔离,适用于嵌套可变对象;
  4. 不可变对象的深浅拷贝等同于赋值,深拷贝可处理循环引用,是数据完全隔离的首选方式
相关推荐
BBB努力学习程序设计2 小时前
深入理解 Python 中的闭包(Closure):封装状态的函数式编程利器
python
澜莲花3 小时前
python图色之opencv基础---验证码实战
开发语言·python·opencv
import_random3 小时前
[python]dataframe二维数据 -- > 三维数据
python
databook3 小时前
格式塔原理:数据可视化如何引导观众的注意力
python·数据分析·数据可视化
秦时明月之君临天下3 小时前
Python递归获取目录大小
python
三万棵雪松4 小时前
【AI小智后端部分(一)】
人工智能·python·ai小智
laplace01234 小时前
Part 3:模型调用、记忆管理与工具调用流程(LangChain 1.0)笔记(Markdown)
开发语言·人工智能·笔记·python·langchain·prompt
winfredzhang4 小时前
深度解析:利用 Python + Playwright 攻克动态网页 PPT 导出难题
python·powerpoint·截图·自动翻页