此文的目的是总结述Python对象的引用、浅拷贝及深拷贝的机制,有助于大家深入的理解其中的含义。
在讲述之前,大家应该很清楚什么是原子类型的对象和容器类的对象。常见的原子类型对象比如数字、字符串等,容器类对象比如列表、元组、字典等。
1.对象的引用
这个比较好理解,为对象创建一个引用。比如赋值操作:
>>> A = 2 #1
>>> B = A #2
在python面向对象编程中,所有类型均可看做是“对象”。#1:把数字“2”看做一个对象的话,那么“A”就是对象的一个引用,相当于c语言中的指针,说白了就是个地址。#2:是把引用“A”又赋给了B,所以“B”也是个对象的引用,且也是指向数字“2”。
如下图所示:
>>> id(A) == id(B)
True
A和B是指向的同一个地址。也就是说,当你创建一个对象,然后把它赋给另一个变量的时候,Python并没有拷贝这个对象,而是拷贝了这个对象的引用。
2.对象的浅拷贝
浅拷贝:对一个对象进行浅拷贝其实是创建了一个类型跟原对象一样,其内容是原来“对象元素”的引用,换句话说,这个拷贝的对象本身是新的(给浅拷贝的对象一个新的id),但是它的内容还是原来的。举个例子:假设你想为一对夫妻创建账户,名为person。然后你分别为他俩拷贝一份(浅拷贝)。
>>> person = ['name',['savings',100.00]]
>>> import copy
>>> hubby = copy.copy(person) #copy模块中的函数
>>> wifey = copy.copy(person) #copy模块中的函数
>>> id(person)
15590344
>>> id(hubby)
15590824
>>> person,hubby
(['name', ['savings', 100.0]], ['name', ['savings', 100.0]])
>>>
如图:
但是,当丈夫取走50后,发生一个奇怪的现象,妻子的账户也减少了50?也就是说,丈夫的行为影响到了他妻子的账户。
>>> hubby[0] = 'joe'
>>> wifey[0] = 'jane'
>>> print(hubby)
['joe', ['savings', 100.0]]
>>> print(wifey)
['jane', ['savings', 100.0]]
>>> hubby[1][1] = 50 #丈夫的账户减少50
>>> hubby,wifey
(['joe', ['savings', 50]], ['jane', ['savings', 50]])
但问题是,当妻子的名字被赋值,为什么丈夫的名字没有受到影响?难道他们的名字不应该都是‘jane’了吗?OK,来解释一下这两个问句。
在文章开头,说明了一下原子类型对象和容器类型对象。第二个问句:就比较好理解了,因为hubby[0]和wifey[0]是原子类型对象,是不可变的,当进行浅拷贝时,就会新创建了一个字符串的对象,所以改变名字没有任何问题。第一个问句:因为hubby[1]是容器类型对象,是个列表。列表本身是可变的,当进行浅拷贝时,列表元素只是把它的引用复制一下。所以,修改了hubby[1]的元素,那么wifey[1]中的内容也随之改变。
3.对象的深拷贝
还是上面的例子,假设需要建立为这对夫妻分别建立各自的账户,互不干扰。这时,就要对其中一个对象进行深拷贝。
>>> person = ['name',['savings',100.00]]
>>> import copy
>>> hubby = person
>>> import copy
>>> wifey = copy.deepcopy(person) #深拷贝
>>> [id(x) for x in (person,hubby,wifey)]
[59752360, 59752360, 53347352]
>>> hubby[0] = 'joe'
>>> wifey[0] = 'jane'
>>> hubby,wifey
(['joe', ['savings', 100.0]], ['jane', ['savings', 100.0]])
>>> hubby[1][1] = 50
>>> hubby,wifey
(['joe', ['savings', 50]], ['jane', ['savings', 100.0]])
作为验证,确认一下所有四个对象都是不同的。
>>> [id(x) for x in hubby]
[7630944, 53346912]
>>> [id(x) for x in wifey]
[7631072, 53347752]
注意:非容器类型没有被拷贝一说,浅拷贝是完全用切片操作来完成的;如果元组变量只包含原子类型对象,对它的深拷贝将不会进行。如果把账号信息改成元组类型,那么即使使用深拷贝操作也只能得到一个浅拷贝。
小结:
- 引用
B = A,id(A) == id(B) 浅拷贝
B = copy.copy(A)。只拷贝第一层元素,若A是嵌套的对象,B不会拷贝嵌套的对象,而是引用嵌套的对象。
id(A) != id(B) ,修改A的第一层元素,B不会变;
id( A[x] ) == id( B[x] ),修改A中嵌套对象中的元素(第二层),B也会变。深拷贝
B = copy.deepcopy(A)。修改A中的任何一个元素,不会影响B。
id(A) != id(B)
若x所在元素是原子类型对象(如数字、字符串),id(A[x]) ==id(B[x]) ,修改A 的第一层元素,B不会变;
若x所在元素是容器对象(如list、dict),id(A[x]) != id(B[x]),修改A中嵌套对象中的元素(第二层),B也不会变。
来段整合的程序——自己分析琢磨吧
import copy
#Python 引用、浅拷贝、深拷贝
#1.原子类型的对象(如数字、字符串)
'''
原子类型的对象没有深拷贝一说,所有的拷贝都是浅拷贝
修改原始数据都会开辟一个新的内存地址,不会影响到拷贝的对象
'''
str1 = 'kyrie'
str2 = str1 #引用
str3 = copy.copy(str1) #浅拷贝
str4 = copy.deepcopy(str1) #深拷贝
print(str1,str2,str3,str4)
print('id(str1) = %d,id(str2) = %d,id(str3) = %d,\
id(str4) = %d' % (id(str1),id(str2),id(str3),id(str4)))
str1 = 666
print(str1,str2,str3,str4)
print('id(str1) = %d,id(str2) = %d,id(str3) = %d,\
id(str4) = %d' % (id(str1),id(str2),id(str3),id(str4)))
#2.容器类型的对象(如list、dict)
def printid(obj,obj_name):
if isinstance(obj,list):
for i in range(len(obj)):
print('id'+ '('+ obj_name +'['+ str(i)+ ']'+')='+str(id(obj[i])),end=' ')
elif isinstance(obj,dict):
for key in obj:
print('id'+ '('+ obj_name +'['+ str(key)+ ']'+')='+str(id(obj[key])),end=' ')
print()
lst = ['a','b',[123,'xyz']]
lst2 = lst #引用
lst3 = copy.copy(lst) #浅拷贝
lst4 = copy.deepcopy(lst) #深拷贝
print(lst,lst2,lst3,lst4)
print('id(lst) = %d,id(lst2) = %d,id(lst3) = %d,\
id(lst4) = %d' % (id(lst),id(lst2),id(lst3),id(lst4)))
printid(lst,'lst')
printid(lst2,'lst2')
printid(lst3,'lst3')
printid(lst4,'lst4')
lst[0] = 'x' #改变lst的第一层元素
lst[2][1] = 'def' #改变lst的第二层元素
print(lst,lst2,lst3,lst4)
print('id(lst) = %d,id(st2) = %d,id(st3) = %d,\
id(st4) = %d' % (id(lst),id(lst2),id(lst3),id(lst4)))
printid(lst,'lst')
printid(lst2,'lst2')
printid(lst3,'lst3')
printid(lst4,'lst4')
结果:
kyrie kyrie kyrie kyrie
id(str1) = 55241632,id(str2) = 55241632,id(str3) = 55241632,id(str4) = 55241632
666 kyrie kyrie kyrie
id(str1) = 50309696,id(str2) = 55241632,id(str3) = 55241632,id(str4) = 55241632
['a', 'b', [123, 'xyz']] ['a', 'b', [123, 'xyz']] ['a', 'b', [123, 'xyz']] ['a', 'b', [123, 'xyz']]
id(lst) = 48833960,id(lst2) = 48833960,id(lst3) = 48834200,id(lst4) = 48834320
id(lst[0])=16985216 id(lst[1])=17022336 id(lst[2])=48833800
id(lst2[0])=16985216 id(lst2[1])=17022336 id(lst2[2])=48833800
id(lst3[0])=16985216 id(lst3[1])=17022336 id(lst3[2])=48833800
id(lst4[0])=16985216 id(lst4[1])=17022336 id(lst4[2])=48834400
['x', 'b', [123, 'def']] ['x', 'b', [123, 'def']] ['a', 'b', [123, 'def']] ['a', 'b', [123, 'xyz']]
id(lst) = 48833960,id(st2) = 48833960,id(st3) = 48834200,id(st4) = 48834320
id(lst[0])=16689984 id(lst[1])=17022336 id(lst[2])=48833800
id(lst2[0])=16689984 id(lst2[1])=17022336 id(lst2[2])=48833800
id(lst3[0])=16985216 id(lst3[1])=17022336 id(lst3[2])=48833800
id(lst4[0])=16985216 id(lst4[1])=17022336 id(lst4[2])=48834400