深入理解 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. 不可变对象的深浅拷贝等同于赋值,深拷贝可处理循环引用,是数据完全隔离的首选方式
相关推荐
我星期八休息12 小时前
Linux系统编程—库制作与原理
linux·运维·服务器·数据结构·人工智能·python·散列表
Cloud_Shy61812 小时前
Python 数据分析基础入门:《Excel Python:飞速搞定数据分析与处理》学习笔记系列(第十二章 用户定义函数 上篇)
python·数据分析·excel·pandas
BU摆烂会噶13 小时前
【LangGraph】House_Agent 实战(四):预定流程 —— 中断与人工干预
android·人工智能·python·langchain
AI玫瑰助手13 小时前
Python运算符:比较运算符(等于不等等于大于小于)与返回值
android·开发语言·python
GIOTTO情13 小时前
Infoseek舆情处置系统的技术实现与落地实践
python
new_dev13 小时前
Python实现Android自动化打包工具:加固、签名、多渠道一键完成
android·python·自动化
天天进步201513 小时前
从零打造 Python 全栈项目:智能教学辅助系统
开发语言·人工智能·python
带带弟弟学爬虫__14 小时前
dyAPP数据采集-个人主页、发布、搜索、评论
服务器·python·算法·flutter·java-ee·django
还是鼠鼠14 小时前
AI掘金头条新闻系统 (Toutiao News)-相关推荐
后端·python·mysql·fastapi·web
数智工坊14 小时前
PyCharm 运行 Python 脚本总自动进 Test 模式?附 RT-DETRv2 依赖缺失终极排坑
开发语言·ide·人工智能·python·pycharm