当前位置: 代码网 > it编程>前端脚本>Python > 一文带你掌握Python中的深浅拷贝

一文带你掌握Python中的深浅拷贝

2026年01月13日 Python 我要评论
第一章:一切从“赋值”引发的问题开始在 python 的世界里,变量不仅仅是存储数据的标签,更像是指向内存中某个对象的“指针”。很多初学者,甚至是有一定

第一章:一切从“赋值”引发的问题开始

在 python 的世界里,变量不仅仅是存储数据的标签,更像是指向内存中某个对象的“指针”。很多初学者,甚至是有一定经验的开发者,都曾在深浅拷贝的问题上栽过跟头。

想象一个场景:你有一个列表,里面包含了一些子列表,你想复制一份用来做修改,保留原数据。于是你顺手写了这样一行代码:

original_list = [[1, 2], [3, 4]]
new_list = original_list

# 现在,我想修改 new_list 的第一个子列表
new_list[0][0] = 999

print("original:", original_list)
print("new:", new_list)

如果你的预期是 original 保持 [[1, 2], [3, 4]],而 new 变为 [[999, 2], [3, 4]],那么很遗憾,现实会给你沉重一击。运行结果是:

original: [[999, 2], [3, 4]]
new: [[999, 2], [3, 4]]

为什么?

这就是 python 中最基础但也最容易被忽视的概念:赋值(assignment)并不是拷贝

在上面的代码中,new_list = original_list 并没有创建一个新的列表对象,它仅仅是创建了一个新的引用(reference)。这就好比你有两个名字(original_listnew_list),但它们都指向同一个实体(内存中的列表对象)。因此,通过任何一个名字去修改这个实体,另一个名字看到的自然也是修改后的样子。

为了彻底解决这个问题,我们需要深入理解 python 内存模型中的三个层次:赋值、浅拷贝和深拷贝

第二章:浅拷贝(shallow copy)——“只复制第一层”

当我们意识到直接赋值不是复制时,我们通常会转向浅拷贝。在 python 中,实现浅拷贝的方法有很多:

  • 切片操作:new_list = old_list[:]
  • 工厂函数:new_list = list(old_list)
  • copy 模块:import copy; new_list = copy.copy(old_list)

让我们看看浅拷贝的表现:

import copy

original_list = [[1, 2], [3, 4]]
shallow_copied_list = copy.copy(original_list)

# 修改外层
shallow_copied_list.append([5, 6])

# 修改内层(嵌套对象)
shallow_copied_list[0][0] = 888

print("original:", original_list)
print("shallow:", shallow_copied_list)

输出结果:

original: [[888, 2], [3, 4]]
shallow: [[888, 2], [3, 4], [5, 6]]

分析:

  • 外层修改shallow_copied_list 增加了一个元素 [5, 6]original_list 没变。这说明最外层的容器确实是新创建的。
  • 内层修改shallow_copied_list[0][0] 被改为 888,original_list 也跟着变了。这说明内层的子列表并没有被复制。

什么是浅拷贝?

浅拷贝(shallow copy)会创建一个新的容器对象,但不会递归地复制容器内的元素。新容器中填充的是原容器中元素的引用

对于不可变对象(如整数、字符串、元组),引用就引用吧,反正改不了。但如果你的列表里包含了可变对象(如列表、字典、集合),那么这些可变对象的引用被共享了,这就是所谓的“共享子对象”(shared sub-objects)。

适用场景:浅拷贝适用于你的数据结构是“扁平”的,或者你明确知道你需要共享子对象(这很少见)。

第三章:深拷贝(deep copy)——“斩断所有羁绊”

如果你需要一个完全独立的副本,无论嵌套多少层,修改副本都不影响原件,那么你需要的是深拷贝

深拷贝使用 copy.deepcopy() 实现:

import copy

original_list = [[1, 2], [3, 4]]
deep_copied_list = copy.deepcopy(original_list)

# 彻底修改副本
deep_copied_list[0][0] = 777
deep_copied_list.append([9, 0])

print("original:", original_list)
print("deep:", deep_copied_list)

输出结果:

original: [[1, 2], [3, 4]]
deep: [[777, 2], [3, 4], [9, 0]]

什么是深拷贝?

深拷贝会递归地遍历原对象的所有子对象,并创建它们的副本。这意味着新对象和原对象在内存中是完全独立的,没有任何引用重叠。

深拷贝的陷阱与高级用法:

虽然深拷贝很强大,但它也有代价(性能开销大)和陷阱。

递归引用导致死循环

如果一个对象直接或间接引用了自己,deepcopy 会抛出 recursionerror

a = [1]
a.append(a) # a 现在是 [1, [...]]
# b = copy.deepcopy(a) # 这会报错

自定义类的拷贝控制:如果你需要控制类的深拷贝行为,可以实现 __deepcopy__ 方法。这在处理数据库连接、文件句柄等不可序列化或不可拷贝的资源时非常有用。

性能考量:对于巨大的数据结构,深拷贝可能非常慢。如果你的嵌套层级很浅,或者全是不可变数据,深拷贝就是杀鸡用牛刀。

第四章:核心原理图解与常见误区

为了彻底理清关系,我们可以通过一张简化的内存示意图来理解:

假设 a = [1, [2, 3]]

赋值 (b = a):

  • a -> [ptr1, ptr2]
  • b -> [ptr1, ptr2] (指向同一个地址)

浅拷贝 (b = copy.copy(a)):

  • a -> [ptr1, ptr2]
  • b -> [ptr3, ptr4]
  • 其中 ptr1 == ptr3 (指向同一个整数 1,整数不可变所以无所谓)
  • 但是 ptr2 == ptr4 (指向同一个列表 [2, 3]) -> 这是问题的根源

深拷贝 (b = copy.deepcopy(a)):

  • a -> [ptr1, ptr2]
  • b -> [ptr5, ptr6]
  • ptr5 指向新的整数 1
  • ptr6 指向一个新的列表 [2, 3],且该新列表内的元素也是新的。

常见误区:字典的copy()方法

很多 python 开发者会直接用字典自带的 .copy() 方法,认为这就是深拷贝。

错误! 字典的 .copy() 也是浅拷贝!

d1 = {'a': [1, 2]}
d2 = d1.copy()
d2['a'].append(3)

print(d1) # 输出 {'a': [1, 2, 3]},d1 被修改了!

正确的做法依然是 copy.deepcopy(d1) 或者使用 d1.copy() 配合字典推导式(如果只有一层的话)。

第五章:总结与最佳实践

搞懂了深浅拷贝,我们其实是在搞懂 python 的对象引用模型。这是编写健壮、无副作用代码的基石。

最后的建议:

  • 默认使用引用:除非你明确需要副本,否则不要随意拷贝。
  • 优先考虑浅拷贝:如果数据结构是扁平的,或者你确定不需要处理嵌套可变对象,list[:]copy.copy() 更快。
  • 不确定时用深拷贝:对于复杂的嵌套结构,为了数据的安全性,copy.deepcopy() 是最稳妥的选择。
  • 警惕函数副作用:在函数中修改传入的可变参数时,一定要想清楚这是不是你想要的行为。如果不想修改原数据,记得在函数内部先拷贝。

以上就是一文带你掌握python中的深浅拷贝的详细内容,更多关于python深浅拷贝的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com