一、深浅拷贝的区别
对于数字和字符串这种不可变对象(不包括tuple),深浅拷贝没有什么意义,如文章所说,只有对组合对象而言有不同,如列表、类实例等。
拷贝分为浅拷贝copy和深拷贝deepcopy。当然,浅拷贝不止copy一种,包括直接复制、切片等等也是。
1.对于数字和字符串而言,没有什么区别,因为他们指向同一个内存地址。
2.而对于字典、元祖、列表等可变量(元组属于不可变量,但它有些特殊,详细的可以参考我这篇)而言,进行赋值、浅拷贝和深拷贝时,其内存地址的变化是不同的。
1)赋值,只是创建一个变量,该变量指向原来内存地址。
>>> a = [1,2]
>>> b = a
a和b的地址完全相同
2)浅拷贝,在内存中只额外创建第一层数据。
>>> from copy import copy
>>> l1 = [1,2]
>>> l2 = l1
>>> l3 = copy(l1)
>>> id(l1), id(l2), id(l3)
(2069879756744, 2069879756744, 2069879213832)
>>> for i,j,k in zip(l1,l2,l3):
print(id(i), id(j), id(k))
1409346000 1409346000 1409346000
1409346032 1409346032 1409346032
可以看到,l1、l2指向的内存地址相同,但它们和l3指向的内存地址不同,但这三个列表其内部包含的元素如1,2指向的地址相同。具体的结果是怎样,可以看第二部分的验证。
3)深拷贝,在内存中将所有的数据重新创建一份(排除最后一层,即:python内部对字符串和数字的优化)
>>> from copy import deepcopy
>>> l1 = [1,2]
>>> l2 = deepcopy(l1)
>>> id(l1), id(l2)
(2069879756744, 2069879960968)
>>> for i,j in (l1,l4):
print(id(i), id(j))
1409346000 1409346032
1409346000 1409346032
目前看来,和浅拷贝的结果类似。l1和l2指向的内存地址不同,其内部元素地址仍然相同。但这是因为其内部元素皆为不可变对象,比如示例代码中的1,2,对应的内存地址仍是相同的,但若是可变对象也不同,请继续看下面的示例。
二、多种复制对象的id(可变对象中包含可变对象)
接下来我们看几个复制的例子:
>>> from copy import copy,deepcopy
>>> a = [1,2]
>>> b = a
>>> c = list(a)
>>> d = copy(a)
>>> e = deepcopy(a)
>>> f = a[:]
>>> id(a),id(b),id(c),id(d),id(e),id(f)
(47481096L, 47481096L, 47429256L, 47431432L, 47784840L, 47475144L)
>>> for i,j,k,m,n,p in zip(a,b,c,d,e,f):
print id(i),id(j),id(k),id(m),id(n),id(p)
30080104 30080104 30080104 30080104 30080104 30080104
30080080 30080080 30080080 30080080 30080080 30080080
这里用了5中拷贝的方法,分别是,直接赋值、list、copy、deepcopy、切片。
可以看到,直接赋值和原列表的id是一样的,其他的都各不相同,但是他们遍历的元素的id却全部相同。
可以看到所有不可变元素id相同,因为python会给你返回一个从缓存中找到的某个类型和值都一致的对象的引用。而如果列表里包含了可变对象,那就不一样了。
>>> h = [3,4,[1,2]]
>>> i = deepcopy(h)
>>> id(h),id(i)
(47506504L, 47516104L)
>>> for m,n in zip(h,i):
print id(m),id(n)
30080056 30080056
30080032 30080032
47507848 47514248
可以看到,那两个整数对象的id仍然相等,但是最后一个列表元素的id就不一样了。当然了,这个列表的列表里的元素的id仍然相等。
>>> for m,n in zip(h[2],i[2]):
print id(m),id(n)
30080104 30080104
30080080 30080080
三、被拷贝对象发生更改之后
>>> a
[1, 2]
>>> a.append(3)
>>> a,b,c,d,e,f
([1, 2, 3], [1, 2, 3], [1, 2], [1, 2], [1, 2], [1, 2])
>>> id(a),id(b),id(c),id(d),id(e),id(f)
(47481096L, 47481096L, 47429256L, 47431432L, 47784840L, 47475144L)
# 之前(47481096L, 47481096L, 47429256L, 47431432L, 47784840L, 47475144L)
可以看到仍旧只有直接赋值的b与原对象a的value同时发生了更改,其他全都没有改变。id也完全没有更改,通过append等方式改变列表,id是不会改变的,除非你重新赋值增加元素,id才会改变。
>>> a = [1,2,3,4]
>>> b,c,d,e,f
([1, 2, 3], [1, 2], [1, 2], [1, 2], [1, 2])
>>> id(a),id(b),id(c),id(d),id(e),id(f)
(47507336L, 47481096L, 47429256L, 47431432L, 47784840L, 47475144L)
如果通过append的方式,id不会发生任何改变。而重新给a赋值,只有a的id改变,其他的id和value仍然没有改变。
四、列表中的列表
>>> from copy import copy,deepcopy
>>> a = ['a',[1,2],[3,4]]
>>> a1 = a
>>> a2 = copy(a)
>>> a3 = deepcopy(a)
>>> a[0]='b'
>>> a[1].append(3)
>>> a[2] = [4,5]
# 在以上这部分做完改变后,再分别输出a,a1,a2,a3会是什么?
>>> a
['b', [1, 2, 3], [4, 5]]
>>> a1
['b', [1, 2, 3], [4, 5]]
>>> a2
['a', [1, 2, 3], [3, 4]]
>>> a3
['a', [1, 2], [3, 4]]
如前面所说,copy和deepcopy都是拷贝,并且拷贝之后与原对象没有关系。
对于deepcopy,原对象是可变量或不可变量,是增删改查都无所谓,它不会做任何更改,它完全是一份新的对象,子元素的id与原先的也完全不同。
而copy,如果是不可变量,则不会发生改变,但若是碰上了可变量,因为它列表里的元素的id和原先的指向的是同一个位置。所以原来的增加了,也会增加。