人人都会AI编程

30.3 深拷贝与浅拷贝、赋值引用的区别

更新时间:2026-07-12

这是一道经典的 Python 面试题,也是日常写代码时频繁踩坑的地方。看似简单,但一旦涉及嵌套的可变对象,结果往往出人意料。

先说结论:

  • 赋值:只是给原对象多贴一个标签,不创建新对象
  • 浅拷贝:创建一个新的外层容器,但内部元素还是指向原来的对象
  • 深拷贝递归地复制所有层级,生成一个与原对象完全独立的新对象。

下面逐一拆解。

赋值:多贴一个标签

a = [1, 2, 3]
b = a           # 赋值,b和a指向同一个列表对象
b.append(4)
print(a)        # [1, 2, 3, 4]  ← a也被改了

b = a 并没有创建新的列表,只是让变量 b 也指向 a 所指向的那个列表对象。修改 b 就是修改那个唯一的列表,a 自然也跟着变。

这可以简单理解为:变量只是对象的引用(标签),赋值绑定的是标签,不是复制对象。

浅拷贝:只复制一层

import copy

a = [1, 2, [3, 4]]
b = copy.copy(a)   # 等价于 a.copy() 或 a[:]

b[0] = 10          # 修改顶层不可变元素,不影响a
b[2][0] = 30       # 修改嵌套列表,会影响到a!
print(a)           # [1, 2, [30, 4]]
print(b)           # [10, 2, [30, 4]]

copy.copy() 创建了一个新的外层列表,但这个新列表内部放的仍然是原来元素的引用。对于不可变元素(如整数、字符串),因为它们不可变,修改时实际是替换引用,所以不会影响原对象;但对于嵌套的可变对象(如内层列表),新列表仍然指向同一个内层列表,修改其内容就会波及原对象。

浅拷贝的常见实现方式:

  • copy.copy(obj)
  • 列表:new_list = old_list.copy()old_list[:]
  • 字典:new_dict = old_dict.copy()
  • 对于只含不可变元素的对象,浅拷贝足够安全。

深拷贝:完全独立

import copy

a = [1, 2, [3, 4]]
b = copy.deepcopy(a)

b[2][0] = 30
print(a)      # [1, 2, [3, 4]]  ← 不受影响
print(b)      # [1, 2, [30, 4]]

copy.deepcopy()递归复制对象中所有层级,生成一个与原对象在内存上完全独立的新对象。修改任意层级都不会影响另一方。

实际场景中的选择

  • 大多数简单场景(列表、字典只含不可变元素)用浅拷贝就够,比如 list.copy()dict.copy()
  • 需要传递或修改复杂嵌套结构(配置字典、树形数据结构、自定义类实例)时,若希望副本完全隔离,就用 copy.deepcopy()
  • 警惕无脑深拷贝:如果对象非常大或包含循环引用,深拷贝可能很慢甚至陷入死循环(不过 Python 的 deepcopy 会处理循环引用,但效率低下,需要留意)。

一个经典踩坑:函数默认参数可变对象

def bad_append(item, target=[]):
    target.append(item)
    return target

print(bad_append(1))  # [1]
print(bad_append(2))  # [1, 2]  ← 同一个默认列表被反复修改

这里默认参数 target=[] 在函数定义时只创建一次,后续所有调用共享同一个列表对象。
正确的做法是用 None 并内部创建:

def good_append(item, target=None):
    if target is None:
        target = []
    target.append(item)
    return target

这个坑本质上也是引用与可变对象的问题,写在这里提醒大家:理解了赋值、浅拷贝、深拷贝,也就更容易识别这类陷阱。

总之,记住一句话:赋值不是复制,浅拷贝只复制一层,深拷贝完全独立。写代码时多看一眼对象是否可变、结构是否嵌套,就能避开绝大多数的复制坑。