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