当前位置:   article > 正文

Python 直接赋值、浅拷贝与深拷贝_深拷贝和直接赋值

深拷贝和直接赋值

有时在进行 Python 编程时,会不会觉得程序的逻辑没有任何问题,但结果却总不是预期想要的?原因很可能就出现在赋值语句中!

首先从概念上直观来感受一下直接赋值、浅拷贝与深拷贝的区别:

  • 直接赋值:其实就是对象的引用(别名)
  • 浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象
  • 深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象

稍显抽象,但通过几个具体例子就可以理解的非常清楚。

直接赋值

a = {'Roma': ['I', 'V']}
b = a

id(a), id(b)
"""
>> (3121395107904, 3121395107904)
"""

b['Roma'].append('X')
print(a)
print(b)
"""
>> {'Roma': ['I', 'V', 'X']}
>> {'Roma': ['I', 'V', 'X']}
"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

直接赋值是对原对象(这里的 a)的引用,即相当于 b 是 a 的别名,除了名字以外并无任何区别,它们都指向同一块内存地址,可以通过 id(a) == id(b) 证实。且因为两者占用同样的内存空间,修改其中一个,另一个也必然随之变化

浅拷贝

浅拷贝会拷贝父对象本身,但不会拷贝父对象内的子对象,举例来说:

c = a.copy()
id(a), id(c)
"""
>> (3121395149440, 3121395270528)
"""

a, c
"""
>> ({'Roma': ['I', 'V', 'X']}, {'Roma': ['I', 'V', 'X']})
"""

c['Arab'] = [1]
a, c
"""
>> ({'Roma': ['I', 'V', 'X']}, 
>>  {'Roma': ['I', 'V', 'X'], 'Arab': [1]})
"""

c['Roma'].append('C')
a, c
"""
>> ({'Roma': ['I', 'V', 'X', 'C']}, 
>>  {'Roma': ['I', 'V', 'X', 'C'], 'Arab': [1]})
"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

也就是说,我们可以把浅拷贝理解为只拷贝了一层,即父对象。此时,通过 id() 我们也可以发现c 和 a 已经不再是同一个对象了。修改 c 将不会对 a 产生影响。如上,我们在 c 中新增了一个键值对元素 c['Arab'] = [1],但 a 并未发生变化。

但是,如果我们对 c 中原本也属于 a 的子对象做修改,a 也会发生相应的变化。a 和 c 此时虽然是独立的对象,但他们的子对象还是指向统一对象(是引用),如下图所示:

在这里插入图片描述

我们也可以通过 id 来验证以上想法:

for key, value in a.items():
    print(id(value)
"""
>> 3121395686848
"""

for key, value in c.items():
    print(id(value)
"""
>> 3121395686848
>> 3121395163328
"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

可以看到,a 和 c 中的 [‘I’, ‘V’, ‘X’, ‘C’] 都指向同一块内存。

此外,我们也可以通过 c = a[:] 或者 c = [ch for ch in a] 来对 a 进行浅拷贝操作。

深拷贝

深度拷贝相当于完全拷贝了父对象及其子对象。此时两个对象完全是两个互不关联的对象,对任何其中做任何修改都不会改变另一个

import copy

d = copy.deepcopy(a)
a['Arab'] = [1, 2, 3]
a['Roma'].append('M')
d['Roma'].append('D')
a, d
"""
>> ({'Roma': ['I', 'V', 'X', 'C', 'M'], 'Arab': [1, 2, 3]},
>>  {'Roma': ['I', 'V', 'X', 'C', 'D']})
"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在这里插入图片描述

上图中 {'Arab': } 未画出。


需要注意的是,拷贝操作只针对一些容器数据类型,例如列表、字典等,对于数字、字符等数据类型,都是直接赋值,即对原对象的引用。


算法题中,我们经常被要求原地对数组或者列表进行操作,我们将这里的原地理解为在算法运行结束之后,仍然会提取原来变量所在的那块内存空间的值(不考虑空间复杂度的影响)。举个例子:

matrix = [1, 2, 3]
id(matrix)
"""
>> 3121410813888
"""

new_matrix = [4, 6, 7]
matrix[:] = new_matrix
id(matrix)
"""
3121410813888
"""

id(new_matrix)
"""
3121411906752
"""
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

我们需要对 数组 matrix 原地操作,此时赋值语句的形式就必须写成浅拷贝的形式,在拷贝完成之和,我们可以看到 matrix 仍然存储在相同的内存空间。

然而,如果我们直接调用 matrix = new_matrix,那么新的 matrix 值将会被存储在新的内存空间,也就是对 new_matrix 的引用。对于原地操作来说,相当于 matrix 的值并未改变。

References

[1] Python中关于列表list的赋值问题
[2] Python 直接赋值、浅拷贝和深度拷贝解析

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/71413
推荐阅读
相关标签
  

闽ICP备14008679号