这是一道经典的 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
这个坑本质上也是引用与可变对象的问题,写在这里提醒大家:理解了赋值、浅拷贝、深拷贝,也就更容易识别这类陷阱。
总之,记住一句话:赋值不是复制,浅拷贝只复制一层,深拷贝完全独立。写代码时多看一眼对象是否可变、结构是否嵌套,就能避开绝大多数的复制坑。