在 Python 中,把一个变量“复制”给另一个变量,往往并没有创建新对象。代码看起来只是简单的一行,但当列表、字典或自定义对象包含可变的嵌套数据时,一次修改可能悄悄影响另一个变量。
理解赋值、浅拷贝和深拷贝之间的差异,是安全处理配置、请求数据、缓存对象和领域模型的基础。
赋值并不复制对象
下面的代码创建了一个列表,然后让两个变量指向它:
original = ["python", "go"]
alias = original
alias.append("rust")
print(original) # ['python', 'go', 'rust']
print(alias) # ['python', 'go', 'rust']
print(original is alias) # True
alias = original 复制的是对象引用,而不是列表本身。两个变量指向同一个对象,因此通过任意一个变量修改列表,另一边都能看到变化。
可以用两个操作符检查对象关系:
is判断两个变量是否指向同一个对象。==判断两个对象的值是否相等。
这一区别在调试复制问题时非常有用。
浅拷贝:复制外层容器,共享内部对象
浅拷贝会创建一个新的外层对象,但不会递归复制其中的成员。Python 的 copy.copy() 可以对多种对象执行浅拷贝;对于列表和字典,也可以使用切片、list.copy() 或 dict.copy()。
import copy
original = {
"name": "api-service",
"tags": ["python", "backend"],
}
cloned = copy.copy(original)
print(original is cloned) # False
print(original["tags"] is cloned["tags"]) # True
cloned["name"] = "worker-service"
cloned["tags"].append("async")
print(original)
print(cloned)
运行结果中,两个字典的 name 可以独立变化,因为外层字典已经分开;但 tags 仍然是同一个列表,因此向副本添加标签也会修改原对象。
常见的浅拷贝写法包括:
items_copy = items.copy()
items_copy = items[:]
settings_copy = settings.copy()
这些写法适合以下情况:
- 容器中只有字符串、整数、元组等不可变值。
- 你明确希望副本和原对象共享内部对象。
- 只需要修改外层结构,例如增加或删除字典顶层字段。
需要注意的是,“外层已经不同”并不代表整个对象图完全独立。
深拷贝:递归创建独立对象图
copy.deepcopy() 会递归复制对象及其嵌套成员。对于多层列表、字典和组合对象,它通常是获得独立副本最直接的方法。
下面是一个可以直接运行的完整示例:
import copy
service_config = {
"service": "checkout",
"database": {
"hosts": ["db-1.internal", "db-2.internal"],
"pool_size": 10,
},
"features": [
{"name": "new-payment-flow", "enabled": False}
],
}
shallow_config = copy.copy(service_config)
deep_config = copy.deepcopy(service_config)
shallow_config["database"]["hosts"].append("db-3.internal")
deep_config["features"][0]["enabled"] = True
print("Original:", service_config)
print("Shallow:", shallow_config)
print("Deep:", deep_config)
print(
"Shared shallow database:",
service_config["database"] is shallow_config["database"],
)
print(
"Shared deep features:",
service_config["features"] is deep_config["features"],
)
这里会出现两种不同结果:
- 修改
shallow_config中嵌套的数据库主机列表,会影响service_config。 - 修改
deep_config中的功能开关,不会影响原始配置。
深拷贝并不意味着所有值都会机械地生成新实例。字符串、整数等不可变对象可以安全共享;deepcopy() 关注的是构造语义上独立的对象图。它还使用内部记忆机制处理重复引用,并能避免在常见循环引用结构中无限递归。
不要把 deepcopy 当成默认按钮
深拷贝提供更强的隔离,但也有成本和边界。
性能与内存开销
复制大型嵌套对象需要遍历对象图,并为大量成员分配内存。如果代码只修改一个顶层字段,浅拷贝通常更清晰、更便宜:
updated = original.copy()
updated["status"] = "ready"
某些资源不适合复制
文件句柄、网络连接、线程锁、数据库会话等对象,不应被理解为普通数据快照。即使某个对象能够通过自定义逻辑完成复制,新副本也未必具备合理的资源语义。
对于这类对象,更稳妥的做法是复制其配置,然后重新创建资源,而不是复制活跃资源本身。
自定义类可以控制复制行为
自定义类可以实现 __copy__() 和 __deepcopy__(),明确哪些字段应该复制、共享或重新初始化。可以这样实践:
import copy
class Job:
def __init__(self, name, options, runtime_cache=None):
self.name = name
self.options = options
self.runtime_cache = runtime_cache or {}
def __deepcopy__(self, memo):
cloned = type(self)(
name=self.name,
options=copy.deepcopy(self.options, memo),
runtime_cache={},
)
memo[id(self)] = cloned
return cloned
job = Job(
"daily-report",
{"recipients": ["ops@example.com"]},
{"last_result": "success"},
)
cloned = copy.deepcopy(job)
cloned.options["recipients"].append("dev@example.com")
print(job.options)
print(cloned.options)
print(cloned.runtime_cache) # 新任务不继承运行时缓存
这里的设计假设是:任务选项需要独立复制,而运行时缓存应该重置。实际项目中应根据领域语义决定,而不是无条件复制所有属性。
选择复制方式的实用清单
面对一个对象时,可以依次问这些问题:
- 是否真的需要副本? 如果只是增加一个变量名,直接赋值即可,但要接受共享修改。
- 只修改外层结构吗? 使用
copy.copy()、列表切片或容器自身的copy()。 - 嵌套可变对象也必须隔离吗? 使用
copy.deepcopy(),并添加测试验证关键字段没有共享。 - 对象中包含连接、锁、缓存或外部资源吗? 避免盲目深拷贝,优先重新构造对象。
- 对象很大或复制频繁吗? 测量性能,并考虑不可变数据结构、按需复制或显式构造新对象。
最可靠的原则不是“总用深拷贝”,而是先决定哪些数据应该共享、哪些状态必须隔离,再选择对应的复制方式。复制策略本质上是对象所有权策略:代码越明确,隐藏的联动修改就越少。