Python 对象复制指南:赋值、浅拷贝与深拷贝到底差在哪

2026-09-27 24 预计阅读时间: 1 分钟
来源: realpython.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:8 分钟

在 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)  # 新任务不继承运行时缓存

这里的设计假设是:任务选项需要独立复制,而运行时缓存应该重置。实际项目中应根据领域语义决定,而不是无条件复制所有属性。

选择复制方式的实用清单

面对一个对象时,可以依次问这些问题:

  1. 是否真的需要副本? 如果只是增加一个变量名,直接赋值即可,但要接受共享修改。
  2. 只修改外层结构吗? 使用 copy.copy()、列表切片或容器自身的 copy()。
  3. 嵌套可变对象也必须隔离吗? 使用 copy.deepcopy(),并添加测试验证关键字段没有共享。
  4. 对象中包含连接、锁、缓存或外部资源吗? 避免盲目深拷贝,优先重新构造对象。
  5. 对象很大或复制频繁吗? 测量性能,并考虑不可变数据结构、按需复制或显式构造新对象。

最可靠的原则不是“总用深拷贝”,而是先决定哪些数据应该共享、哪些状态必须隔离,再选择对应的复制方式。复制策略本质上是对象所有权策略:代码越明确,隐藏的联动修改就越少。


相关推荐