Python copy.copy()与deepcopy()用法及代码示例

Oliver
Oliver
发布时间:2026-10-28| 9人浏览过
原创

深入详解 Python 中 copy.copy() 浅拷贝与 copy.deepcopy() 深拷贝的用法、底层内存引用机制、代码示例、不可变对象优化、循环引用处理以及性能与选型避坑指南。

在 Python 中,将一个变量赋值给另一个变量(如 b = a)并不会创建新的对象,而只是让两个变量指向同一块内存地址。当数据结构内部包含列表、字典或集合等可变子元素时,这种共享引用的特性极易引发意料之外的数据被修改问题。为了解决这一痛点,Python 标准库提供了 copy 模块,其中包含两个核心函数:copy.copy()(浅拷贝) 与 copy.deepcopy()(深拷贝)。本文将通过原理剖析、内存图解、对比示例与避坑指南,帮助你彻底掌握两者的用法与适用边界。

一、直接赋值、浅拷贝与深拷贝的本质区别

假设我们正在开发一个系统配置管理模块,初始配置列表中包含子选项列表。如果直接使用 = 赋值并修改副本体,原配置也会被同步篡改;如果使用浅拷贝,修改外层列表互不影响,但修改嵌套子列表时原配置仍会发生变动;只有使用深拷贝,所有嵌套层级才会被完全递归复制。

为了直观展现三者在内存引用上的差异,请观察下方的内存模型对比图:

Python copy.copy()与deepcopy()用法及代码示例
图 1:直接赋值、浅拷贝与深拷贝在内存地址与引用层级上的本质差异

二、copy.copy() 浅拷贝用法与代码示例

copy.copy(x) 返回对象 x 的浅层副本。它会为外层容器创建一个全新的对象,但容器内部所包含的各个元素的引用并未被递归复制,依然指向原有对象的内存地址。

1. 一维列表:浅拷贝的表现如同完全复制

对于仅包含整数、浮点数、字符串等不可变类型的一维列表,由于元素本身无法在原地被修改,浅拷贝已经能够提供完全独立的修改体验:

import copy

# 原始列表(纯一维不可变元素)
original_list = [10, 20, 30]

# 使用 copy.copy 进行浅拷贝
shallow_copied = copy.copy(original_list)

# 修改副本的第一个元素
shallow_copied[0] = 999

print(f"原始列表: {original_list}")
print(f"副本列表: {shallow_copied}")
print(f"外层内存地址是否相同: {id(original_list) == id(shallow_copied)}")
示例运行输出
原始列表: [10, 20, 30] 副本列表: [999, 20, 30] 外层内存地址是否相同: False

可以看到,外层对象的 id 不同,修改 shallow_copied[0] 并没有对 original_list 造成任何影响。

2. 嵌套结构:浅拷贝的“致命”盲区

一旦列表中包含了其他可变对象(如嵌套的子列表、字典),浅拷贝就会暴露出其“只拷贝第一层”的局限性:

import copy

# 包含嵌套子列表的对象
original_data = ["服务器A", ["CPU: 4核", "内存: 16G"]]

# 浅拷贝
copied_data = copy.copy(original_data)

# 场景1:修改外层顶层元素(独立)
copied_data[0] = "服务器B"

# 场景2:修改嵌套子列表内的元素(共享!)
copied_data[1][0] = "CPU: 8核"

print("--- 修改后的最终结果 ---")
print(f"原始数据: {original_data}")
print(f"副本数据: {copied_data}")
print(f"子列表内存地址对比: id(original_data[1])={id(original_data[1])}, id(copied_data[1])={id(copied_data[1])}")
示例运行输出
--- 修改后的最终结果 --- 原始数据: ['服务器A', ['CPU: 8核', '内存: 16G']] 副本数据: ['服务器B', ['CPU: 8核', '内存: 16G']] 子列表内存地址对比: id(original_data[1])=140234850123008, id(copied_data[1])=140234850123008

注意:虽然外层容器 copied_data 独立被改为了“服务器B”,但由于子列表 original_data[1] 和 copied_data[1] 的内存地址完全相同,修改副本的子列表直接导致原始数据的 CPU 配置也被篡改。

3. Python 内置语法中的浅拷贝等价形式

在 Python 中,除了调用 copy.copy() 以外,以下常见语法在底层全部执行的是浅拷贝:

  • 列表切片:new_list = old_list[:]
  • 工厂函数:new_list = list(old_list)、new_dict = dict(old_dict)
  • 容器自带方法:new_list = old_list.copy()、new_dict = old_dict.copy()
  • 列表推导式:[x for x in old_list]

三、copy.deepcopy() 深拷贝用法与代码示例

copy.deepcopy(x) 是解决嵌套引用共享的终极武器。它不仅复制顶层容器,还会以递归方式向下遍历对象所包含的全部层级,为每一个遇到的子对象、孙对象分配全新的内存空间并复制其内容。

1. 彻底隔离嵌套对象实战

使用深拷贝重写上一节中的服务器配置示例:

import copy

# 具有多层嵌套的复杂字典
base_cluster = {
    "cluster_name": "Prod-Cluster",
    "nodes": [
        {"ip": "192.168.1.101", "role": "master", "tags": ["k8s", "control-plane"]},
        {"ip": "192.168.1.102", "role": "worker", "tags": ["k8s", "compute"]}
    ]
}

# 使用 deepcopy 进行深层复制
staging_cluster = copy.deepcopy(base_cluster)

# 全面修改副本的各级数据
staging_cluster["cluster_name"] = "Staging-Cluster"
staging_cluster["nodes"][0]["ip"] = "10.0.0.1"
staging_cluster["nodes"][0]["tags"].append("test-env")

print("=== 原始集群配置 ===")
print(f"名称: {base_cluster['cluster_name']}")
print(f"Master IP: {base_cluster['nodes'][0]['ip']}")
print(f"Master Tags: {base_cluster['nodes'][0]['tags']}")

print("\n=== 新集群副本配置 ===")
print(f"名称: {staging_cluster['cluster_name']}")
print(f"Master IP: {staging_cluster['nodes'][0]['ip']}")
print(f"Master Tags: {staging_cluster['nodes'][0]['tags']}")
示例运行输出
=== 原始集群配置 === 名称: Prod-Cluster Master IP: 192.168.1.101 Master Tags: ['k8s', 'control-plane'] === 新集群副本配置 === 名称: Staging-Cluster Master IP: 10.0.0.1 Master Tags: ['k8s', 'control-plane', 'test-env']

可以看到,无论嵌套有多深,对 staging_cluster 任意节点的标签列表进行 append 操作,base_cluster 都完全保持纯洁,彼此之间实现了真正的数据隔离。

四、拷贝方式选型与排查步骤流程

在实际工程开发中,盲目使用 deepcopy() 会带来不必要的 CPU 计算和内存开销;而侥幸使用浅拷贝又容易埋下隐蔽的数据污染漏洞。根据业务数据特征选择最佳拷贝路径,可参考以下标准化推导流程:

Python copy.copy()与deepcopy()用法及代码示例
图 2:Python 拷贝模式选型推导与执行验证步骤图

五、底层机制与特殊场景深度剖析

1. 不可变对象的“不拷贝”优化机制

对于不可变类型(如 int、float、str 以及只包含不可变元素的 tuple),无论是调用 copy.copy() 还是 copy.deepcopy(),Python 解释器在底层都会进行内存优化:直接返回原有对象的引用,而不开辟新的内存。因为既然对象无法改变,共享其只读引用是绝对安全的,还能节省内存并提升运行效率。

import copy

# 不可变字符串与不可变元组
text = "hello-python"
immutable_tuple = (1, 2, "abc")

# 浅拷贝与深拷贝
copied_text = copy.copy(text)
deep_tuple = copy.deepcopy(immutable_tuple)

print(f"字符串 ID 是否相同: {id(text) == id(copied_text)}")
print(f"纯不可变元组 ID 是否相同: {id(immutable_tuple) == id(deep_tuple)}")

# 特殊情况:包含可变对象的元组
hybrid_tuple = (1, [2, 3])
deep_hybrid = copy.deepcopy(hybrid_tuple)
print(f"含可变列表的元组 deepcopy 后 ID 是否相同: {id(hybrid_tuple) == id(deep_hybrid)}")
print(f"内部列表 ID 是否相同: {id(hybrid_tuple[1]) == id(deep_hybrid[1])}")
示例运行输出
字符串 ID 是否相同: True 纯不可变元组 ID 是否相同: True 含可变列表的元组 deepcopy 后 ID 是否相同: False 内部列表 ID 是否相同: False

由此可见:只要元组内部包含了可变子对象,deepcopy() 就会毫不犹豫地创建新元组并递归克隆其子列表。

2. 循环引用与 Memo 字典机制

如果一个对象直接或间接引用了自身(例如图结构或链表环),递归复制难道不会陷入无限死循环并触发 RecursionError 崩溃吗?

标准库 deepcopy 已经内置了优雅的防死循环机制:在深拷贝执行期间,它会维护一个 memo 备忘录字典(即 {id(原对象): 复制出的新对象})。每当复制一个对象前,首先查验其内存地址是否已存在于 memo 中;如果存在,直接从 memo 获取对应引用返回,彻底粉碎死循环。

Python copy.copy()与deepcopy()用法及代码示例
图 3:copy.deepcopy 内部 memo 备忘录缓存与循环引用解析机制

我们用实际代码运行验证循环引用的深拷贝:

import copy

# 构造自引用循环列表
node_a = [100]
node_a.append(node_a)

print(f"原对象长度: {len(node_a)}")
print(f"原对象是否引用自身: {node_a[1] is node_a}")

# 执行深拷贝
cloned_node = copy.deepcopy(node_a)

print(f"克隆对象长度: {len(cloned_node)}")
print(f"克隆对象是否引用克隆自身: {cloned_node[1] is cloned_node}")
print(f"克隆对象与原对象是否独立: {cloned_node is not node_a}")
示例运行输出
原对象长度: 2 原对象是否引用自身: True 克隆对象长度: 2 克隆对象是否引用克隆自身: True 克隆对象与原对象是否独立: True

3. 自定义类支持:重写 __copy__ 与 __deepcopy__

当标准拷贝逻辑无法满足复杂类的业务需求时,可以在自定义类中定义 __copy__() 与 __deepcopy__(memo) 魔术方法实现精准控制:

import copy

class DatabaseSession:
    def __init__(self, username, query_history=None):
        self.username = username
        self.query_history = query_history if query_history is not None else []
        self.connection_id = id(self)  # 模拟连接句柄

    def __copy__(self):
        # 浅拷贝:共享查询历史,新建会话对象
        print("-> 触发了 __copy__ 自定义协议")
        new_obj = type(self)(self.username, self.query_history)
        return new_obj

    def __deepcopy__(self, memo):
        # 深拷贝:深层递归复制查询历史,生成全新独立连接
        print("-> 触发了 __deepcopy__ 自定义协议")
        new_history = copy.deepcopy(self.query_history, memo)
        new_obj = type(self)(self.username, new_history)
        memo[id(self)] = new_obj
        return new_obj

# 测试自定义类的拷贝行为
sess1 = DatabaseSession("admin", ["SELECT * FROM users"])
sess_deep = copy.deepcopy(sess1)

sess_deep.query_history.append("UPDATE configs SET status=1")

print(f"原始历史记录数: {len(sess1.query_history)}")
print(f"深拷贝历史记录数: {len(sess_deep.query_history)}")
示例运行输出
-> 触发了 __deepcopy__ 自定义协议 原始历史记录数: 1 深拷贝历史记录数: 2

六、性能对比与避坑指南

在性能敏感的大型系统或高频调用接口中,拷贝方式对 CPU 与内存的占用不可忽视。我们对包含了 5,000 个字典的大型列表进行性能基准耗时测试(示例测量):

操作方式 平均耗时 (ms) 内存分配 对嵌套可变对象的防护
直接赋值 (b = a) < 0.001 ms 0 字节 (仅指针引用) 无(完全共享)
浅拷贝 (copy.copy / [:]) 0.12 ms 仅外层容器结构 仅限第一层,子对象不防篡改
深拷贝 (copy.deepcopy) 14.85 ms 全量递归创建每一个子对象 完全防御,绝对安全解耦

实战开发核心避坑指南

  1. 不要对包含不可拷贝资源的对象使用 deepcopy:如果对象包含操作系统层级的资源(例如打开的文件描述符、线程锁 threading.Lock、网络套接字 socket 或数据库连接池),调用 deepcopy 会抛出 TypeError: cannot pickle '_io.TextIOWrapper' object 异常。对此类对象应显式过滤或重写 __deepcopy__。
  2. 函数默认参数陷阱:定义函数时千万不要用 def func(data=[]):,应使用 def func(data=None): if data is None: data = []。如果传入了可变参数且函数内部需要做临时处理,记得先使用 copy.copy() 或 copy.deepcopy() 进行隔离保护。
  3. 单层切片误以为是深拷贝:许多初学者习惯写 data_copy = [row[:] for row in matrix],如果矩阵内部还有第三层对象,依然是共享引用的。

七、Python 拷贝全景速查与总结

为了便于日常开发随时查阅,下图对 Python 中的四种引用与拷贝方式进行了全维度的横向对比与总结:

Python copy.copy()与deepcopy()用法及代码示例
图 4:Python 拷贝与引用机制全景横向速查总结
相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。