R / Richie全部文章 ↑

Python · 3 分钟阅读

深拷贝与浅拷贝

在 Python 中,赋值 并不复制对象——它只是把同一个对象的另一个 名字
贴到一个对象上。真正的复制有两种:浅拷贝 和 深拷贝。理解它们的区别
能避免掉进“改了 A,结果 B 也跟着变”的坑。


1. 赋值 ≠ 拷贝

a = [1, 2, [3, 4]]
b = a               # 同一对象
b.append(5)

print(a)            # [1, 2, [3, 4], 5]   —— a 跟着变了
print(a is b)       # True               —— 身份相同

= 不会调用 copy.copy,它只是让两个名字指向同一个对象。要“真的复制”,
必须显式调用 copy / deepcopy(或使用切片、构造、推导式等语言机制)。


2. 浅拷贝

新对象,但里面的 子对象仍然共享。

import copy

original = [1, 2, [3, 4]]
shallow  = copy.copy(original)         # 等价于 original[:]

shallow.append(99)                     # 只影响 shallow
shallow[2][0] = 'changed'              # 影响 original!

print(original)   # [1, 2, ['changed', 4]]
print(shallow)    # [1, 2, ['changed', 4], 99]

浅拷贝的常见等价写法:

写法 适用对象
copy.copy(x) 任意对象
x[:] 列表 / 字符串
list(x) / dict(d) / set(s) 构造方式复制
x.copy() 列表 / 字典 / 集合
copy.copy(x) 自定义对象

3. 深拷贝

新对象 + 所有子对象递归地也是新的。

import copy

original = [1, 2, [3, 4]]
deep     = copy.deepcopy(original)

deep[2][0] = 'changed'

print(original)   # [1, 2, [3, 4]]    —— 不受影响
print(deep)       # [1, 2, ['changed', 4]]

deepcopy 会沿着 __dict__、__slots__、容器递归。如果对象里持有
外部资源(文件、socket、线程…)或者循环引用,库函数都能正确处理。


4. 图示

赋值(a = b):
    a ─┐
       ├──▶ [1, 2, ━► [3, 4]]     (同一对象)
    b ─┘

浅拷贝(b = a.copy()):
    a ──▶ [1, 2, ━► [3, 4]]
              ↑          ↑
    b ──▶ [1, 2] ─┘      │
                     子对象共用

深拷贝(b = deepcopy(a)):
    a ──▶ [1, 2, ─► [3, 4]]
    b ──▶ [1, 2, ─► [3', 4']]    (完全独立)

5. 常见误区

5.1 列表里装同样的子对象

a = [[0]] * 3
a[0][0] = 9
print(a)   # [[9], [9], [9]]    —— 三个子列表是同一个!

# 正确写法
a = [[0] for _ in range(3)]

5.2 字典里装可变默认值

import copy

d = {"k": [1, 2, 3]}
d2 = d.copy()
d2["k"].append(4)         # 浅拷贝:原 dict 的 list 也会被改
print(d)                  # {'k': [1, 2, 3, 4]}

d3 = copy.deepcopy(d)
d3["k"].append(5)         # 深拷贝:互不影响
print(d)                  # {'k': [1, 2, 3, 4]}
print(d3)                 # {'k': [1, 2, 3, 4, 5]}

5.3 自定义对象的 __copy__ / __deepcopy__

想让自定义类的复制行为更精细,可以重写:

import copy

class Connection:
    def __init__(self, url):
        self.url = url
        self.session = open_session()    # 假设是个不可复制的资源

    def __copy__(self):
        new = self.__class__(self.url)
        new.session = None              # 不复制 session
        return new

    def __deepcopy__(self, memo):
        new = self.__class__(self.url)
        new.session = None
        memo[id(self)] = new
        return new

6. 性能

deepcopy 比 copy 慢,因为它要 递归 复制每层。对于纯不可变对象,Python
会做一定优化:例如 deepcopy((1, 2, 3)) 其实只是返回了原对象——因为元组 +
数字都是不可变的。

import copy, sys

big = [list(range(1000)) for _ in range(1000)]

%timeit copy.copy(big)        # 几十微秒
%timeit copy.deepcopy(big)    # 几十毫秒

真要复制一个上百万元素的嵌套结构时,先考虑是否真的需要深拷贝——
也许你只需要一个临时浅拷贝 + 按需 deepcopy 某个子结构。


7. 选型建议

场景 推荐
顶层不可变(int / str / tuple / frozenset) 随便用 = / 浅拷贝
顶层可变,但只读不改 copy / [:]
要把子对象彻底隔离 deepcopy
自定义类(持有资源) 重写 __copy__ / __deepcopy__
用 JSON 序列化作为“深拷贝” 看 JSON 替代方案

8. JSON 替代方案

如果对象里只装“基本类型 + 字典 + 列表 + 字符串”,可以借 json 当成
deepcopy
:简单、快、跨语言可移植。

import json

obj = {"users": [{"name": "a", "tags": [1, 2]}]}
clone = json.loads(json.dumps(obj))      # “深拷贝”

# 缺点:tuple -> list,datetime -> str,set 没法处理。

9. 小结

  • a = b 不是复制,是同一对象的另一个名字。
  • 浅拷贝:复制一层,子对象共享。
  • 深拷贝:递归复制,所有层级独立。
  • 选哪种看业务:会不会改子对象?会,就 deepcopy。
  • 自定义类可以重写 __copy__ / __deepcopy__ 精细控制复制行为。