Python学习系列之浅拷贝和深拷贝

本文深入探讨了Python中对象的赋值与拷贝机制,详细解释了浅拷贝与深拷贝的区别,以及它们在处理复杂对象时的行为差异。

Python中,对象的赋值拷贝(深/浅拷贝)之间是有差异的,如果使用的时候不注意,就可能产生意外的结果。

id

什么是id?一个对象的id值在Python解释器里就代表它在内存中的地址。

对两个id值相同的对象进行操作相当于处理同一个地址内的数据(java里是引用对象,C里是指针),两个对象同时发生变化。

>>> a=[1,2,3]
>>> b=a
>>> id(a)
"""
4382960392
"""
>>> id(b)
"""
4382960392
"""
>>> id(a)==id(b)    #附值后,两者的id相同,为true。
True
>>> b[0]=222222  #此时,改变b的第一个值,也会导致a值改变。
>>> print(a,b)
[222222, 2, 3] [222222, 2, 3] #a,b值同时改变

赋值

赋值后,原始对象与赋值对象是同一个对象,两个引用指向同一个对象。如果该对象是引用类型(也就是可变类型,值类型就是不可变类型),则相当于操纵同一个内存地址:修改其中一个对象,则两个对象都发生变化。

a = 1
b = 2
a = 1
b = a
b = 2
print(a, b)
(1, 2)
a = [1, 2, 3]
b = a
b[1] = 3
a
Out[47]: [1, 3, 3]
b
Out[48]: [1, 3, 3]

 因此出现了拷贝的概念及函数。

If you need to modify the sequence you are iterating over while inside the loop (for example to duplicate selected items), it is recommended that you first make a copy. Iterating over a sequence does not implicitly make a copy.

– Python Documentation

浅拷贝

拷贝父对象,不会拷贝对象的内部的子对象。它复制了对象,但对于对象中的元素,依然使用原始的引用(只拷贝目标对象的引用)。

当使用浅拷贝时,python拷贝外层对象的数据(对象内部的对象则是通过赋值的方式,所以还是同一个内部对象。也就是对于内部对象,两个对象是共享内存),此时两个对象不再是同一个对象。相当于新建立一个对象。并且把原来对象的内部数据(包括内部对象)传递到新建立的对象里。

>>> import copy
>>> a=[1,2,3]
>>> c=copy.copy(a)  #拷贝了a的外围对象本身,
>>> id(a)
43209864
>>> id(c)
43219336
>>> print(id(a)==id(c))  #id 改变 为false
False
>>> c[1]=22222   #此时,我去改变c的第二个值时,a不会被改变。
>>> print(a,c)
[1, 2, 3] [1, 22222, 3] #a值不变,c的第二个值变了,这就是copy和‘==’的不同

而对于内部对象则共享内存。修改其中一个对象的内部对象,另一个对象的内部对象也会修改。

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

43196360
43219336
False
([1, [1, 2], 3], [1, [1, 2], 3])

因为内部对象是共享内存的,所以引出了深拷贝的概念。 

 

当我们使用下面的操作的时候,会产生浅拷贝的效果: 

  • 使用切片[:]操作
  • 使用工厂函数(如list/dir/set)
  • 使用copy模块中的copy()函数

 

深拷贝

copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。深拷贝拷贝一个对象的引用,以及它里面的所有元素(包含元素的子元素)

deepcopy对对象内部数据和外部引用都进行了拷贝,也就是完全拷贝。不但新创建一个对象,而且在对象内部的每一个对象都是新创建的,然后把原始对象内部数据传入新创建的对象里。

#copy.copy()

>>> a=[1,2,[3,4]]  #第三个值为列表[3,4],即内部元素
>>> d=copy.copy(a) #浅拷贝a中的[3,4]内部元素的引用,非内部元素对象的本身
>>> id(a)==id(d)
False
>>> id(a[2])==id(d[2])
True
>>> a[2][0]=3333  #改变a中内部原属列表中的第一个值
>>> d             #这时d中的列表元素也会被改变
[1, 2, [3333, 4]]


#copy.deepcopy()

>>> e=copy.deepcopy(a) #e为深拷贝了a
>>> a[2][0]=333 #改变a中内部元素列表第一个的值
>>> e
[1, 2, [3333, 4]] #因为是深拷贝,这时e中内部元素[]列表的值不会因为a中的值改变而改变

总结 

The difference between shallow and deep copying is only relevant for compound objects (objects that contain other objects, like lists or class instances):

  • A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.
  • A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.

– Python Doctumentation

两种 copy 只在面对复杂对象时有区别,所谓复杂对象,是指对象中含其他对象(如复杂的 list 和 class)。

由 shallow copy 建立的新复杂对象中,每个子对象,都只是指向自己在原来本体中对应的子对象。而 deep copy 建立的复杂对象中,存储的则是本体中子对象的 copy,并且会层层如此 copy 到底。

(1)对于不可变类型和简单的可变类型(也就是可变类型里包含的对象都是不可变类型,也就是不存在可变类型嵌套问题),浅拷贝和深拷贝无区别。

(2)1、copy.copy 浅拷贝 只拷贝父对象,不会拷贝对象的内部的子对象。2、copy.deepcopy 深拷贝 拷贝对象及其子对象。

(3)使用深拷贝时,需要注意以下两个问题:

  • 递归对象拷贝: Recursive objects (compound objects that, directly or indirectly, contain a reference to themselves) may cause a recursive loop.
  • 大对象拷贝: Because deep copy copies everything it may copy too much, e.g., administrative data structures that should be shared even between copies.

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值