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

 更新时间:2026年01月13日 09:33:57   作者:小庄-Python办公  
本文将深入解析Python中的深浅拷贝问题,通过实例演示赋值,浅拷贝和深拷贝的区别,这篇文章剖析了字典.copy()方法的误区,并给出最佳实践建议,希望对大家有所帮助

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

在 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深浅拷贝的资料请关注脚本之家其它相关文章!

相关文章

  • 浅析Python函数式编程

    浅析Python函数式编程

    在本篇文章中我们给大家分享了关于Python函数式编程的相关知识点内容,有兴趣的朋友参考下。
    2018-10-10
  • 一文带你玩转Python必备的几种数据格式

    一文带你玩转Python必备的几种数据格式

    在Python开发中,数据格式的选择直接影响着程序的性能和可维护性,本文将详细介绍Python开发中最常用的几种数据格式,希望可以帮助大家选择最合适的数据表示方式
    2025-06-06
  • 安装scrapy框架并测试全过程

    安装scrapy框架并测试全过程

    本文介绍了如何安装和测试Scrapy框架,并分享了创建爬虫项目的过程,包括在PyCharm中创建工程、编写爬虫主文件以及在settings.py中进行配置,通过运行爬虫文件,验证了安装和配置的正确性
    2025-11-11
  • Python装饰器使用示例及实际应用例子

    Python装饰器使用示例及实际应用例子

    这篇文章主要介绍了Python装饰器使用示例及实际应用例子,本文给出了斐波拉契数列、注册回调函数、mysql封装、线程异步等实际使用示例,需要的朋友可以参考下
    2015-03-03
  • Python切片知识解析

    Python切片知识解析

    这篇文章主要介绍了Python切片知识解析的相关资料,需要的朋友可以参考下
    2016-03-03
  • 如何用python批量发送工资条邮件

    如何用python批量发送工资条邮件

    大家好,本篇文章主要讲的是如何用python批量发送工资条邮件,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-01-01
  • 如何学习Python time模块

    如何学习Python time模块

    在本篇文章里小编给大家分享的是关于Python time模块知识点及用法,需要的朋友们可以学习下。
    2020-06-06
  • 使用python绘制地图的示例代码

    使用python绘制地图的示例代码

    要在Python中绘制地图,你可以使用各种库和工具包,其中最常用的是matplotlib和folium,本文就来给大家介绍一下如何使用python绘制地图,文章通过代码示例介绍的非常详细,需要的朋友可以参考下
    2023-08-08
  • python 字符串常用方法超详细梳理总结

    python 字符串常用方法超详细梳理总结

    字符串是Python中基本的数据类型,几乎在每个Python程序中都会使用到它。本文为大家总结了Python中必备的31个字符串方法,需要的可以参考一下
    2022-03-03
  • Python之PyQt6对话框的实现

    Python之PyQt6对话框的实现

    这篇文章主要介绍了Python之PyQt6对话框的实现,文章内容详细,简单易懂,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2023-01-01

最新评论