Python 3 拷贝Python对象、浅拷贝和深拷贝

此文的目的是总结述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 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值