商城首页欢迎来到中国正版软件门户

您的位置: 首页 > 文章列表 > 编程开发 > Python深拷贝与浅拷贝的实现及对比详解(很多人都理解错了)

Python深拷贝与浅拷贝的实现及对比详解(很多人都理解错了)

  发布于2026-07-06 阅读(0)

扫一扫,手机访问

前言

在 Python 的世界里,有一道几乎每次面试都会被问到的经典题目:浅拷贝(Shallow Copy)和深拷贝(Deep Copy)到底差在哪?很多开发者,甚至写过几年代码的老手,遇到嵌套结构时也容易翻车——明明只是想复制一份数据,结果改了一个另一个也跟着变了。更让人头疼的是,Bug 出现时根本想不到是拷贝方式的问题。

Python深拷贝与浅拷贝的实现及对比详解(很多人都理解错了)

今天,咱们就把这个话题彻底拆开揉碎,从底层原理到实战避坑,一步到位说清楚。

一、从一个简单的例子开始

先上一段代码,猜猜输出结果是什么:

python
original = [1, 2, [3, 4]]
copy1 = original.copy()          # 浅拷贝
copy2 = original[:]              # 也是浅拷贝

original[2][0] = 99
original[0] = 100

print(original)   # [100, 2, [99, 4]]
print(copy1)      # ???
print(copy2)      # ???

如果你觉得 copy1copy2 都还是 [1, 2, [3, 4]],那你和绝大多数初学者掉进了同一个坑。实际结果是这样的:

python
print(copy1)   # [1, 2, [99, 4]]
print(copy2)   # [1, 2, [99, 4]]

为什么第一个元素没变,内部的列表却跟着改了?答案就藏在“浅拷贝”的本质里。

二、Python 中的对象与引用

在讨论拷贝之前,得先弄清楚 Python 里变量和对象的关系。一句话概括:变量名只是个标签,真正的数据住在内存里的对象上。

python
a = [1, 2, 3]
b = a          # b 和 a 引用同一个对象
b.append(4)
print(a)       # [1, 2, 3, 4]   a 也被改了

b = a 并没有创造新列表,只是让 b 指向 a 指向的那个对象。这种操作叫引用赋值

拷贝则不同——它会新建一个对象,但新对象和原对象的关系取决于复制深度。

三、浅拷贝(Shallow Copy)

1. 什么是浅拷贝

浅拷贝会生成一个新容器,但容器里的每个元素仍然是对原对象中对应元素的引用。用大白话说:外壳换了个新的,但里面的东西还是原来的那批。

2. 浅拷贝的实现方式

Python 提供了好几种写法,本质都是一样的:

  • copy 模块的 copy() 函数:import copy; new_obj = copy.copy(obj)
  • 列表专用:list.copy()list[:]list(list_obj)
  • 字典专用:dict.copy()dict(dict_obj)
  • 集合专用:set.copy()
python
import copy

lst = [1, 2, [3, 4]]
lst_copy = copy.copy(lst)   # 浅拷贝
lst_slice = lst[:]           # 也是浅拷贝
lst_construct = list(lst)    # 浅拷贝

print(lst_copy is lst)       # False,外层对象不同
print(lst_copy[2] is lst[2]) # True,内部列表是同一个

3. 浅拷贝的行为演示

python
original = [1, 2, [3, 4]]
shallow = original.copy()

original[0] = 100        # 修改外层不可变元素(int)
original[2][0] = 99      # 修改内部可变列表

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

修改 original[0] 时,整数是不可变对象,shallow[0] 仍然指向原来的整数 1,所以不受影响。

修改 original[2][0] 时,因为 original[2]shallow[2] 指向的是同一个列表对象,所以变动的影子也投到了 shallow 身上。

总结一句话:浅拷贝只管外层是新对象,内部的元素全是原对象的引用。

四、深拷贝(Deep Copy)

1. 什么是深拷贝

深拷贝会把原对象里的所有元素递归地复制一遍,最终得到一个彻彻底底独立的新对象。原对象和新对象之间没有任何共享的引用,你改你的,我改我的,互不干扰。

2. 深拷贝的实现方式

Python 只提供了 copy.deepcopy() 这一种标准做法:

python
import copy

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

print(deep is lst)        # False
print(deep[2] is lst[2])  # False,内部列表也被复制了

3. 深拷贝的行为演示

python
import copy

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

original[0] = 100
original[2][0] = 99

print(original)   # [100, 2, [99, 4]]
print(deep)       # [1, 2, [3, 4]]  完全不受影响

深拷贝实现了真正的“备份”——无论结构多复杂,新的就是新的,旧的还是旧的。

五、深入理解:自定义对象的拷贝

1. 自定义类与 __copy__、__deepcopy__

对于自定义的类,copy.copy()copy.deepcopy() 会优先调用类里定义的 __copy__()__deepcopy__() 方法。如果没定义,默认行为是:copy.copy() 会创建一个新实例并递归浅拷贝所有属性;copy.deepcopy() 则递归深拷贝所有属性。

当然,你也可以手动控制:

python
import copy

class MyClass:
    def __init__(self, data):
        self.data = data

    def __copy__(self):
        # 自定义浅拷贝行为
        new_instance = type(self)(self.data[:])   # 假设 data 是列表
        return new_instance

    def __deepcopy__(self, memo):
        # 自定义深拷贝行为
        new_instance = type(self)(copy.deepcopy(self.data, memo))
        return new_instance

2. 不可变对象的特殊情况

对于 intstrtuple 这类不可变对象,浅拷贝和深拷贝没有本质区别——反正你也改不了它。但注意:如果元组内部包含了可变对象,深拷贝仍然会递归复制那些可变部分。

python
import copy

tup = (1, 2, [3, 4])
tup_copy = copy.copy(tup)
tup_deep = copy.deepcopy(tup)

print(tup_copy is tup)        # True,元组本身不可变,copy 可能返回原对象
print(tup_deep is tup)        # False,deepcopy 会递归复制内部列表
print(tup_deep[2] is tup[2])  # False

六、浅拷贝与深拷贝的对比

特性浅拷贝深拷贝
复制层级只复制最外层对象递归复制所有层级
内存占用小,共享内部对象大,完全独立
速度
与原对象关系内部可变对象会相互影响完全独立,互不影响
适用场景简单数据结构,无需完全隔离复杂嵌套结构,需要完全隔离

七、容易踩的坑

坑1:把 = 当成了拷贝

python
a = [1, 2, 3]
b = a
b.append(4)
print(a)   # [1, 2, 3, 4]   a 也被改了

老生常谈但反复有人犯错——= 只是多贴了一张标签,不是复制数据。

坑2:误以为 copy() 能保护所有层级

python
original = [[1, 2], [3, 4]]
shallow = original.copy()
shallow[0][0] = 99
print(original)  # [[99, 2], [3, 4]]  原数据被改了!

明明用了 copy(),原数据却还是被污染了。原因就是浅拷贝只复制了外层列表,内部子列表依然是引用。

坑3:深拷贝遇到循环引用会不会死循环?

python
import copy

a = []
b = [a]
a.append(b)   # 形成循环引用

c = copy.deepcopy(a)   # 默认可以处理,不会无限递归
print(c)  # [[[...]]]

deepcopy 内部用了一个 memo 字典来记录已经复制过的对象,所以循环引用能安全搞定。

坑4:有些对象根本不能深拷贝

文件对象、线程锁、数据库连接……这些“重量级”对象通常无法复制。如果硬要深拷贝,会抛出异常。解决方案是自定义 __deepcopy__ 方法,返回自身或一个合理的替代品。

python
import copy

f = open('test.txt', 'w')
# copy.deepcopy(f)   # TypeError: cannot pickle '_io.TextIOWrapper' object

八、实际应用场景

场景1:防御性编程

当你把一个对象传给不可信的函数,又不想让函数偷偷改掉你的数据,深拷贝就派上用场了。

python
def process(data):
    # 可能会修改 data,但不想影响外部
    data_local = copy.deepcopy(data)
    # ... 操作 data_local

场景2:缓存与状态保存

实现撤销、重做功能时,需要保留对象的历史快照。深拷贝能保证每个历史版本互不干扰。

python
class Document:
    def __init__(self):
        self.content = []
    def sa ve_state(self):
        return copy.deepcopy(self.content)
    def restore_state(self, state):
        self.content = state

场景3:多线程编程

多个线程要独立处理同一份数据的副本,深拷贝可以避免竞态条件——每个线程拿到的都是自己的副本。

场景4:配置管理

多个模块基于同一份默认配置生成各自的独立配置时,深拷贝防止互相篡改。

九、性能考量与最佳实践

1. 避免不必要的深拷贝

深拷贝可能很慢,尤其是处理大型对象时。只在真正需要隔离时才用。如果只需要复制一层,浅拷贝就够用了。

python
# 如果只需要复制一层,用浅拷贝
new_list = old_list[:]

# 如果内部元素是不可变对象,浅拷贝就够了
nums = [1, 2, 3]
new_nums = nums[:]   # 安全,因为整数不可变

2. 自定义复制方法提高效率
对于自定义类,重写 __copy____deepcopy__ 可以避开不必要的复制(比如共享那些只读的大块数据)。

3. 利用 copyreg 模块注册复制函数
如果遇到第三方库里的类型无法直接复制,可以通过 copyreg 模块注册一个复制函数来解决。

十、总结

1. 浅拷贝:建新壳,壳里的东西还是原来的引用。适合简单结构或不需要完全隔离的场景。

2. 深拷贝:从头到尾复制一遍,新东西和旧东西之间毫无瓜葛。适合复杂嵌套且要求完全隔离的场景。

3. 赋值(=)不是拷贝,只是给同一个对象贴了多个标签。

4. 不可变对象的浅拷贝和深拷贝区别不大,但如果它内部藏了可变对象,深拷贝依然会复制那些可变的部分。

5. 使用 copy 模块时,记住性能开销——深拷贝花了更多时间和内存,别滥用。

本文转载于:https://www.jb51.net/python/363435hf0.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。

热门关注