蒙珣的博客

活好当下,做好今天该做的事情。

0%

Python深拷贝与浅拷贝

Python的引用计数

首先我们要知道,Python 内不可变对象的内存管理方式是引用计数。因此,我们在谈论浅拷贝时,其实谈论的主要特点都是基于可变对象的。

1
2
3
4
5
6
7
8
9
10
11
import copy

a = 'hello' # python的不可变对象:元组,数值,字符串
b = a
c = copy.copy(a)
d = copy.deepcopy(c)

print(id(a))
print(id(b))
print(id(c))
print(id(d))

结果

1
2
3
4
4310112880
4310112880
4310112880
4310112880

因为我们这里操作的是不可变对象,Python 用引用计数的方式管理它们,所以 Python 不会对值相同的不可变对象,申请单独的内存空间。只会记录它的引用次数

操作不可变对象时,由于引用计数的特性,浅拷贝和深拷贝是没有区别的

浅拷贝(Shallow Copy)

我们先来比较一下浅拷贝和赋值在可变对象上的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import copy

a = ['123']

b = a

c = copy.copy(a)

d = copy.deepcopy(c)

print(id(a))
print(id(b))
print(id(c))
print(id(d))

结果

1
2
3
4
4372413312
4372413312
4372889152
4372889216

赋值就是对物体进行贴标签的操作,作用于同一个物体

浅拷贝则会创建一个新的对象,至于对象中的元素,它依然会引用原来的物体,请看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
import copy

a = ['123']

c = copy.copy(a)

print(id(a))
print(id(c))

a[0] = '555'

print(f"a的值为: {a}, a的内存地址为: {id(a)}")
print(f"c的值为: {c}, c的内存地址为: {id(c)}")

结果

1
2
3
4
4337646464
4338122688
a的值为: ['555'], a的内存地址为: 4337646464
c的值为: ['123'], c的内存地址为: 4338122688


由于浅拷贝会使用原始元素的引用(内存地址)。所以在操作被拷贝对象内部可变元素时,其结果是会影响到拷贝对象的

现在,我们看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import copy

a = [['aaa'],'bbb']

print(a)

c = copy.copy(a)
print(c)

a[0][0] = 'tom'
a[1] = 'jack'

print(a)
print(c)

深拷贝(Deep Copy)

深拷贝遇到可变对象,则又会进行一层对象创建,所以你操作被拷贝对象内部的可变对象,不影响对象内部的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import copy

a = [['aaa'],'bbb']

print(f"a的值为: {a}")

c = copy.copy(a)
d = copy.deepcopy(a)
print(f"浅拷贝c的值为: {c}")
print(f"深拷贝d的值为: {d}")

a[0][0] = 'tom'
a[1] = 'jack'
print('-------------')
print(f"修改后a的值为: {a}")
print(f"浅拷贝c的值为: {c}")
print(f"深拷贝d的值为: {d}")

结果

1
2
3
4
5
6
7
a的值为: [['aaa'], 'bbb']
浅拷贝c的值为: [['aaa'], 'bbb']
深拷贝d的值为: [['aaa'], 'bbb']
-------------
修改后a的值为: [['tom'], 'jack']
浅拷贝c的值为: [['tom'], 'bbb']
深拷贝d的值为: [['aaa'], 'bbb']

总结

  1. 由于Python内部引用计数器的特性,对于不可变对象,浅拷贝和深拷贝的作用是一样的,指针指向同一个地址
  2. 浅拷贝的拷贝,其实是拷贝了原始元素的引用(内存地址),所以当拷贝可变对象时,原对象内可变对象的对应元素的改变,会在复制对象的对应元素上,有所体现
  3. 深拷贝遇到可变对象时,又在内部新建了一个副本。所以,不管它内部的元素如何变化,都不会影响到原来副本的可变对象

实际应用场景

那么讲了这么多,他们的实际应用场景又是什么呢?这也是网上讲的最少的部分了

  1. 避免修改原始数据:当你需要修改一个对象,但又不想影响原始数据时,深拷贝是一个很好的选择。

    1
    2
    3
    4
    5
    6
    7
    8
    import copy

    original_dict = {'a': 1, 'b': [2,3]}
    deep_copy = copy.deepcopy(original_dict)

    deep_copy['b'][0] = 4
    print(original_dict)
    print(deep_copy)

    结果

    1
    2
    {'a': 1, 'b': [2, 3]}
    {'a': 1, 'b': [4, 3]}
  2. 配置文件的处理:当你从配置文件中读取数据并希望对其进行修改,但同时又希望保留原始配置文件的内容时,深拷贝非常有用

  3. 多线程/多进程中数据安全:在多线程或多进程环境中,你可能需要确保一个对象在多线程或多进程之间是独立的。通过深拷贝,你可以为每个线程或进程提过一个独立的对象副本

注意:深拷贝可能会消耗更多的内存和时间,因为它需要递归的复制对象的所有内容。因此,在选择使用深拷贝还是浅拷贝时,需要权衡你具体需求(如性能、内存使用等)和场景

引用

5张图彻底理解Python中的浅拷贝与深拷贝

Python 直接赋值、浅拷贝和深度拷贝解析