Python浅拷贝与深拷贝

参考:
Python 的深拷贝和浅拷贝
Python的变量与对象(不可变对象与可变对象)


0. 前提

对于不可变对象,无论深、浅拷贝,内存地址 (id) 都是一成不变的;
对于可变对象,则存在 3 种不同情况:直接赋值、浅拷贝、深拷贝

不可变对象与可变对象的知识点可参考:Python的变量与对象(不可变对象与可变对象)

1. 直接赋值

直接赋值:仅拷贝了对可变对象的引用,故前后变量均未隔离,任一变量 / 对象改变,则所有引用了同一可变对象的变量都作相同改变。例如:

>>> x = [555, 666, [555, 666]]
>>> y = x  # 直接赋值, 变量前后并未隔离
>>> y
[555, 666, [555, 666]]
 
# 修改变量 x, 变量 y 也随之改变
>>> x.append(777)  
>>> x
[555, 666, [555, 666], 777]
>>> y
[555, 666, [555, 666], 777]
 
# 修改变量 y, 变量 x 也随之改变
>>> y.pop()
777
>>> y
[555, 666, [555, 666]]
>>> x
[555, 666, [555, 666]]

在某些情况下,这是致命的,因此还需要深、浅拷贝来正确实现真正所需的拷贝目的。

2. 浅拷贝

浅拷贝:使用 copy(x) 函数,拷贝可变对象如 list 的最外层对象并实现隔离,但 list 内部的嵌套对象仍是未被隔离的引用关系。例如:

>>> import copy
>>> x = [555, 666, [555, 666]]
>>> z = copy.copy(x)  # 浅拷贝
>>> zz = x[:]  # 也是浅拷贝, 等同于使用 copy() 函数的 z
>>> z
[555, 666, [555, 666]]
>>> zz
[555, 666, [555, 666]]
 
# 改变变量 x 的外围元素, 不会改变浅拷贝变量
>>> x.append(777)
>>> x
[555, 666, [555, 666], 777]  # 只有自身改变, 增加了外围元素 777
>>> z
[555, 666, [555, 666]]  # 未改变
>>> zz
[555, 666, [555, 666]]  # 未改变
 
# 改变变量 x 的内层元素, 则会改变浅拷贝变量
>>> x[2].append(888)
>>> x
[555, 666, [555, 666, 888], 777]  # 同时发生改变, 增加了内层元素 888
>>> z
[555, 666, [555, 666, 888]]  # 同时发生改变, 增加了内层元素 888
>>> zz
[555, 666, [555, 666, 888]]  # 同时发生改变, 增加了内层元素 888
 
# 浅拷贝变量的外围元素改变不会相互影响
>>> z.pop(0)
555
>>> x
[555, 666, [555, 666, 888], 777]  # 未改变
>>> z
[666, [555, 666, 888]]  # 只有自身改变, 弹出了外围元素 555
>>> zz
[555, 666, [555, 666, 888]]  # 未改变
 
# 浅拷贝变量的内层元素改变会相互影响
>>> z[1].pop()
888
>>> x
[555, 666, [555, 666], 777]  # 同时发生改变, 弹出了内层元素 888
>>> z
[666, [555, 666]]  # 同时发生改变, 弹出了内层元素 888
>>> zz
[555, 666, [555, 666]]  # 同时发生改变, 弹出了内层元素 888

注意,所谓改变应包含 “增、删、改” 三种,以上仅展示了前两种情况,第三种不言自明。

此外,若有人问元组 (tuple) 一定是不可变的吗?答案是不一定,因为浅拷贝时仅隔离最外层对象,而内层嵌套对象则仍为引用关系,例如:

>>> t = (1, 2, [3, 4])  # tuple
>>> import copy
>>> ct = copy.copy(t)  # 浅拷贝 tuple  # 注意, 令 ct = t 时此例结果仍然相同
>>> ct
(1, 2, [3, 4])
>>> ct[2][-1] = 5  # 修改 ct
>>> ct
(1, 2, [3, 5])
>>> t  # t 也随之改变, 证明内层嵌套对象仍为引用关系
(1, 2, [3, 5])

3. 深拷贝

深拷贝:使用 deepcopy(x[,memo]) 函数,拷贝可变对象如 list 的“外围+内层”而非引用,实现对前后变量不论深浅层的完全隔离。例如:

>>> import copy
>>> x = [555, 666, [555, 666]]
>>> k = copy.deepcopy(x)  # 深拷贝
>>> k
[555, 666, [555, 666]]
 
# 改变变量 x 的外围元素, 不会改变深拷贝变量
>>> x.append(777)
>>> x
[555, 666, [555, 666], 777]
>>> k
[555, 666, [555, 666]]  # 未改变
 
# 改变变量 x 的内层元素, 同样不会改变深拷贝变量
>>> x[2].append(888)
>>> x
[555, 666, [555, 666, 888], 777]
>>> k
[555, 666, [555, 666]]  # 未改变
 
# 深拷贝变量的外围元素改变不会相互影响
>>> k.pop(0)
555
>>> x
[555, 666, [555, 666, 888], 777]  # 未改变
>>> k
[666, [555, 666]]
 
# 深拷贝变量的内层元素改变同样不会相互影响
>>> k[1].pop()
666
>>> x
[555, 666, [555, 666, 888], 777]  # 未改变
>>> k
[666, [555]]

再次试验元组 (tuple) 的例子以展示浅拷贝和深拷贝的区别与联系:

>>> t = (1, 2, [3, 4])  # tuple
>>> import copy 
>>> ct = copy.deepcopy(t)  # 深拷贝 tuple 
>>> ct
(1, 2, [3, 4])
>>> ct[2][-1] = 5  # ct 改变
>>> ct
(1, 2, [3, 5])
>>> t  # t 不论外层还是内层嵌套变量, 均不变 (完全隔离)
(1, 2, [3, 4])

4. 代码示例

4.1 列表浅拷贝与深拷贝

import copy
a = [1,2,3,[4,5,6]]
b = a                 # 直接赋值: 引用对象,原始对象的父对象及子对象均被引用
c = a.copy()          # 浅拷贝:深拷贝父对象(一级目录),子对象(二级目录)不拷贝,还是引用
d = copy.deepcopy(a)  # 深拷贝,包含对象里面的自对象的拷贝,所以原始对象的改变不会造成深拷贝里任何子元素的改变

a.append('AA')
a[3].append('BB')

print("a:", a)
print("b:", b)
print("c:", c)
print("d:", d)


输出:

a: [1, 2, 3, [4, 5, 6, 'BB'], 'AA']
b: [1, 2, 3, [4, 5, 6, 'BB'], 'AA']
c: [1, 2, 3, [4, 5, 6, 'BB']]
d: [1, 2, 3, [4, 5, 6]]

4.2 字典浅拷贝与深拷贝

import copy
dict1 = {'user': 'A',
         'num': [1, 2, 3]}

dict2 = dict1         # 直接赋值: 引用对象,原始对象的父对象及子对象均被引用
dict3 = dict1.copy()  # 浅拷贝:深拷贝父对象(一级目录),子对象(二级目录)不拷贝,还是引用
dict4 = copy.deepcopy(dict1) # 深拷贝

dict1['user'] = 'BBB'
dict1['num'].remove(1)

# 输出结果
print("dict1: ", dict1)
print("dict2: ", dict2)
print("dict3: ", dict3)
print("dict4: ", dict4)


输出:

dict1:  {'user': 'BBB', 'num': [2, 3]}
dict2:  {'user': 'BBB', 'num': [2, 3]}
dict3:  {'user': 'A', 'num': [2, 3]}
dict4:  {'user': 'A', 'num': [1, 2, 3]}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

吮指原味张

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

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

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

打赏作者

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

抵扣说明:

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

余额充值