跳到主要内容

深拷贝与浅拷贝

对象、变量与引用

理解深拷贝和浅拷贝前,需要先区分变量和对象。Python 变量保存的是对对象的引用,赋值通常只是让另一个变量引用同一个对象,并不会自动复制对象。

original = [1, 2, 3]
alias = original

alias.append(4)

print(original) # [1, 2, 3, 4]
print(alias is original) # True

alias = original 只是增加了一个引用。两个变量指向同一列表,因此通过任意一个变量修改列表,另一个变量都能观察到变化。

浅拷贝

浅拷贝(Shallow Copy)会创建一个新的外层容器,但不会递归复制容器内部的对象。新旧容器中的嵌套元素仍然指向相同对象。

创建浅拷贝

列表常见的浅拷贝方式包括:

import copy

original = [1, 2, 3]

copy_by_method = original.copy()
copy_by_slice = original[:]
copy_by_constructor = list(original)
copy_by_module = copy.copy(original)

字典和集合也提供 copy()

settings = {"theme": "dark", "features": ["search"]}
settings_copy = settings.copy()

tags = {"python", "copy"}
tags_copy = tags.copy()

浅拷贝的嵌套对象

original = {
"name": "Alice",
"skills": ["Python", "SQL"],
}
shallow = original.copy()

shallow["name"] = "Bob"
shallow["skills"].append("Git")

print(original["name"]) # Alice
print(shallow["name"]) # Bob
print(original["skills"]) # ['Python', 'SQL', 'Git']

修改 shallow["name"] 只替换了新字典中的引用,不影响原字典;但两个字典的 skills 都引用同一个列表,所以对列表执行 append() 会同时反映在两边。

可以通过 is 验证对象身份:

print(shallow is original)                    # False
print(shallow["skills"] is original["skills"]) # True

深拷贝

深拷贝(Deep Copy)不仅创建新的外层对象,还会递归复制其中包含的可变对象,使副本尽量形成独立的对象结构。

Python 使用 copy.deepcopy() 创建深拷贝:

import copy

original = {
"name": "Alice",
"skills": ["Python", "SQL"],
}
deep = copy.deepcopy(original)

deep["skills"].append("Git")

print(original["skills"]) # ['Python', 'SQL']
print(deep["skills"]) # ['Python', 'SQL', 'Git']
print(deep["skills"] is original["skills"]) # False

深拷贝会维护一个已经复制过的对象记录,因此能够保留原对象内部的共享关系,并避免在常见循环引用中无限递归。

import copy

original = []
original.append(original)

deep = copy.deepcopy(original)
print(deep is deep[0]) # True

核心区别

假设对象结构如下:

外层列表
├── 整数 1
└── 内层列表 [2, 3]

不同操作的结果可以概括为:

直接赋值:外层和内层都共享
浅拷贝:外层独立,内层共享
深拷贝:外层独立,内层也独立
对比项直接赋值浅拷贝深拷贝
是否创建新外层对象
是否递归复制嵌套对象
嵌套可变对象是否共享通常否
常用写法alias = originalcopy.copy().copy()、切片copy.deepcopy()
时间与内存成本最低较低较高
典型用途有意共享同一对象只需独立修改外层结构需要独立修改整个对象图

不可变对象的影响

整数、浮点数、字符串、布尔值等不可变对象不能被原地修改,因此复制操作可能安全地复用它们:

import copy

name = "Python"
deep_name = copy.deepcopy(name)

print(deep_name is name) # 在当前实现中通常为 True

这并不违反深拷贝的语义,因为不可变对象无法被一方修改后影响另一方。

元组本身不可变,但元组可能包含可变对象:

import copy

original = ([1, 2], "fixed")
deep = copy.deepcopy(original)

deep[0].append(3)
print(original[0]) # [1, 2]

判断是否需要深拷贝时,应关注整个对象结构中是否包含可能被修改的嵌套对象,而不能只看最外层类型。

自定义对象

浅拷贝和深拷贝同样适用于类实例:

import copy

class User:
def __init__(self, name, skills):
self.name = name
self.skills = skills

original = User("Alice", ["Python"])
shallow = copy.copy(original)
deep = copy.deepcopy(original)

shallow.skills.append("SQL")
deep.skills.append("Git")

print(original.skills) # ['Python', 'SQL']
print(deep.skills) # ['Python', 'Git']

类可以实现 __copy__()__deepcopy__() 自定义复制行为。例如,数据库连接、文件句柄、线程锁等外部资源通常不适合被机械地深拷贝,应由类明确决定共享、重建或禁止复制。

常见误区

二维列表的乘法初始化

以下写法不会创建三个相互独立的内层列表:

matrix = [[0] * 3] * 3
matrix[0][0] = 1

print(matrix)
# [[1, 0, 0], [1, 0, 0], [1, 0, 0]]

三个位置引用的是同一个内层列表。应使用列表推导式分别创建:

matrix = [[0] * 3 for _ in range(3)]
matrix[0][0] = 1

print(matrix)
# [[1, 0, 0], [0, 0, 0], [0, 0, 0]]

切片不是深拷贝

original[:] 只复制列表外层。列表包含嵌套列表、字典或类实例时,这些对象仍然共享。

深拷贝不一定适合所有对象

深拷贝不是默认最安全的选择。它可能:

  • 复制大量数据,消耗更多时间和内存;
  • 复制本应共享的状态,破坏对象之间的关系;
  • 无法合理复制文件、网络连接、锁等外部资源;
  • 让程序依赖隐式复制,而不是明确的数据所有权。

如何选择

  • 希望多个变量操作同一状态:直接赋值;
  • 只会增加、删除或替换外层元素,嵌套对象可以共享:浅拷贝;
  • 副本需要独立修改嵌套可变对象:深拷贝;
  • 数据规模较大或对象包含外部资源:优先设计明确的复制方法,只复制真正需要的字段;
  • 数据本身适合不可变设计:可以使用不可变对象,减少复制和共享状态带来的问题。

选择复制方式的核心不是嵌套层数,而是副本和原对象之间是否允许共享可变状态。