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

本文介绍了Python编程中赋值、浅拷贝和深拷贝的区别。直接赋值实际上是对象引用,修改一个会影响另一个。浅拷贝仅拷贝父对象,但子对象仍共享内存,而深拷贝则完全复制父对象及所有子对象,形成独立的对象。在算法和数据结构操作中,理解这些概念至关重要,避免因赋值方式不当导致的意外行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

有时在进行 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']}
"""

直接赋值是对原对象(这里的 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]})
"""

也就是说,我们可以把浅拷贝理解为只拷贝了一层,即父对象。此时,通过 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
"""

可以看到,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']})
"""

在这里插入图片描述

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


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


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

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

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

id(new_matrix)
"""
3121411906752
"""

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

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

References

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

如松茂矣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值