Pthon浅拷贝与深拷贝

MJX2022/11/16Python深浅拷贝

变量赋值

在介绍深浅拷贝时我们先回顾一下之前讲过的 变量赋值原理列表在内存中的存储方式

在 Python 中,变量赋值只是简单地将对象的一个 引用 指派给了变量。

比如下面的代码:

a = [1, 2, 3]
b = a

这里 ab 都通过赋值引用同一个列表 [1, 2, 3] 。如果对该列表的任意一个元素进行修改,则另一个变量也会受到影响,因为它们引用的都是同一个对象:

b[0] = 4
print(a)
print(b)
# 两均个输出 [4, 2, 3]

这种行为叫做引用,也就是说变量 ab 引用的是同一个列表对象。在 Python 中,可以使用 id() 函数获取任何对象的唯一标识符来观察此现象:

print(id(a), id(b))  # 输出两个相同的数字,代表同一个内存地址

根据之前所讲我们可以用下面图片来表示一下ab在内存中的关系:

列表赋值的内存表示.png

接下来介绍 Python 中的浅拷贝和深拷贝:

浅拷贝

对于 不可变类型 来说,浅拷贝如果修改值,则会重新开辟内存空间用来存储变量值,对原来的变量不会产生影响。对于 可变类型 来说,浅拷贝仅仅拷贝了可变对象在内存中的地址,没有重新开辟内存空间。

如以下代码:

import copy

# 定义列表 a 和 b
a = [1, 2, [3, 4], 5]
b = copy.copy(a)   # 执行浅拷贝

# 修改列表 b 的第二个元素
b[1] = 100

# 查看列表 a 和 b 的值
print("列表 a:", a)
print("列表 b:", b)

执行以上代码,输出的结果为:

列表 a: [1, 2, [3, 4], 5]
列表 b: [1, 100, [3, 4], 5]

可以看到,修改列表 b 中的某个元素并不会影响到列表 a。

但如果改变的是嵌套列表或字典等 可变类型 中的元素时,就不一样了。

如以下代码:

# 定义列表 a 和 b
a = [1, 2, [3, 4], 5]
b = copy.copy(a)   # 执行浅拷贝

# 修改列表 b 中嵌套列表的第一个元素
b[2][0] = 100

# 查看列表 a 和 b 的值
print("列表 a:", a)
print("列表 b:", b)

执行以上代码,输出的结果为:

列表 a: [1, 2, [100, 4], 5]
列表 b: [1, 2, [100, 4], 5]

可以看到,修改列表 b 中的嵌套列表的某个元素,也会影响到列表 a,这是因为浅拷贝只复制了嵌套列表(也就是上面的[3,4])在内存中的地址,并没有开辟新的内存空间。

上面两个代码块执行过程内存图示如下:

浅拷贝.png

在 Python 中,可以使用以下方法来执行浅拷贝:

  • 切片操作:列表或其他可变序列通常支持切片操作。a[:] 创建了一个新列表,它与原始列表 a 包含相同的元素。这个新列表是浅拷贝的,因为它们共享相同的对象。

    a = [[1, 2], [3, 4]]
    b = a[:]
    print(id(a[0]), id(b[0]))
    
  • 使用 copy( ) 函数:使用该函数可以创建并返回新的浅拷贝对象。

    import copy
    
    a = [[1, 2], [3, 4]]
    b = copy.copy(a)
    print(id(a[0]), id(b[0]))
    

当执行浅拷贝操作时,只有 最外层 对象是新的,被浅拷贝的子对象仍然是原来的引用。

深拷贝

深拷贝会重新分配内存,对拷贝后的对象进行修改,不会影响原始对象。

如以下代码:

import copy

# 定义列表 a 和 b
a = [1, 2, [3, 4], 5]
b = copy.deepcopy(a)   # 执行深拷贝

# 修改列表 b 中的第一个元素以及嵌套列表的第一个元素
b[0] = 100
b[2][0] = 1000

# 查看列表 a 和 b 的值
print("列表 a:", a)
print("列表 b:", b)

执行以上代码,输出的结果为:

列表 a: [1, 2, [3, 4], 5]
列表 b: [100, 2, [1000, 4], 5]

可以看到,修改列表 b 中的某个元素并不会影响到列表 a,这是因为深拷贝不仅复制了最外层的嵌套元素,还 递归 进行复制嵌套列表中的元素,开辟新的内存空间。总的来说 深拷贝就是完完全全的拷贝

综上所述,浅拷贝和深拷贝的主要区别在于:

  • 浅拷贝仅仅是复制 可变对象 在内存中的地址,不会重新分配内存;深拷贝会递归复制嵌套对象中的元素,重新分配内存。
  • 浅拷贝对于简单对象进行复制时(如整数、字符串),和深拷贝没有什么区别。但浅拷贝无法复制嵌套对象的完整结构,只是复制了对象在内存中的地址。