浅谈Python中的深拷贝与浅拷贝

狂风中的少年 提交于 2020-03-06 11:32:22

明确概念

要理解深拷贝和浅拷贝,我们要明白Python中的一个概念:当一个 变量 = xxx 的时候,即为这个变量指向了xxx

例如:a = [11,22]  即a指向了一个地址,这个地址中存放着[11,22]

初识深拷贝、浅拷贝

浅拷贝

a = [11, 22]
b = a

print(id(a))
print(id(b))

a.append(33)
print(a)
print(b)

结果(id一致)

1157228286408
1157228286408

[11, 22, 33]
[11, 22, 33]

 

 

 

  浅拷贝,b拷贝了a指向。a指向[11,22],a=b使得b也指向[11,22],当列表append时,a和b都指向列表,所以都增加。

深拷贝

import copy


a = [11, 22]
c = copy.deepcopy(a)


print(id(a))
print(id(c))

a.append(33)
print(a)
print(c)

结果(id不一致,a增加以后,c不增加)

2312887095752
2312887096264

[11, 22, 33]
[11, 22]

 

 

 c深拷贝a,c指向了另一个地址,该地址将a指向的列表完全拷贝了一份,此时a和c没有任何关系,当a指向的列表append后,c指向的列表不会append。

浅拷贝

浅拷贝是对于一个对象的顶层拷贝。通俗的理解是:拷贝了引用,并没有拷贝内容

运用copy模块进行演示

import copy
a = [11, 22]
b = [33, 44]
c = [a, b]
e = copy.copy(c)

print(id(c))
print(id(e))

print(id(c[0]))
print(id(e[0]))

a.append(55)
print(c)
print(e)

结果

2323393015432
2323373459848

2323343954376
2323343954376

[[11, 22, 55], [33, 44]]
[[11, 22, 55], [33, 44]]

  

 

e = copy.copy(c)e只拷贝c,而其中c中指向的数据不会改变。

使得e指向一个新的地址来拷贝c,但其中的数据依然指向a和b,当a增加时,e也会增加。

 

那么d=cd = copy.copy(c)有什么不同呢?

d = c为d和c相等,也指向了a和b,a或b或c变化时d也会变化

a = [11, 22]
b = [33, 44]
c = [a, b]
d = c

print(id(c))
print(id(d))

a.append(55)
c.append([66,77])
print(c)
print(d)  

结果

1999078026952
1999078026952

[[11, 22, 55], [33, 44], [66, 77]]
[[11, 22, 55], [33, 44], [66, 77]]

       从上面可以看出,d和c完全相等,d和c指向的地址一致,即d等于c,该地址指向了a和b。所以当a或b列表变化时,d列表发生了变化;c列表发生变化时,d列表页发生了变化。

 

浅拷贝拷贝的是最顶层,指向对象为a和b,与拷贝的c无关系,c增加时,d不会增加。

import copy
a = [11, 22]
b = [33, 44]
c = [a, b]
d = copy.copy(c)

print(id(c))
print(id(d))

a.append(55)
c.append([66,77])
print(c)
print(d)

结果

2794624642760
2794576124296

[[11, 22, 55], [33, 44], [66, 77]]
[[11, 22, 55], [33, 44]]  

   d = copy.copy(c)d在一个新的地址中拷贝了c,此时d和c没有关系,从上面可以知道,d和c中存储的变量都是指向a和b的,所有当c列表append的时候,d不会增加,而当a或b列表增加时,d列表指向a类别的变量增加了。

总结:

  • d=c让d这个变量指向c指向的空间

  • d=copy.copy(c)复制所有c指向的数据到一个新空间,但是不会递归copy

 

深拷贝

深拷贝是对于一个对象所有层次的拷贝(递归)

import copy
a = [11, 22]
b = [33, 44]
c = [a, b]
d = copy.deepcopy(c)

print(id(c))
print(id(d))

print(id(c[0]))
print(id(d[0]))

a.append(55)
print(c)
print(d)  

结果

1806966386312
1806917863752

1806917298248
1806966385096

[[11, 22, 55], [33, 44]]
[[11, 22], [33, 44]]

 d = copy.deepcopy(c)创建一个新的地址,不仅拷贝了c,也拷贝了一份c中的数据,指向了另一方a和b。此时d的指向的a和b完全与c无关,当c指向的a或b列表发生变化时,d指向的a和b列表不会发生变化。

 

拷贝对象为不可变量的情况

现在我们拷贝的对象都是可变的(列表),那么拷贝不可变量(元组)会发生什么情况呢?需要我来探讨下。

拷贝元组

import copy

a = (11, 22)
b = copy.copy(a)

print(id(a))
print(id(b))  

结果

1778596640456
1778596640456

注意:copy.copy()如果拷贝元组,不会进行拷贝,仅仅是指向。

原因:元组是不可遍历型,无法进行数据的修改,因此copy.copy()会进行判断,如果copy元组变成指向。

copy.deepcopy()深拷贝同理,大家可以自己试一试。

 

如果元组中存着变量为可变类型呢?又会发生什么呢?

import copy

a = [11,22]
b = [33,44]

c = (a,b)

e = copy.copy(c)
d = copy.deepcopy(c)

print(id(c))
print(id(e))
print(id(d))

a.append(55)
print(c)
print(e)
print(d)  

结果

1885947170760
1885947170760
1885947222856

([11, 22, 55], [33, 44])
([11, 22, 55], [33, 44])
([11, 22], [33, 44])

从结果可以看出,浅拷贝依然为指向,指向了c,相当于e = c,当a或b发生变化时,e也发生了变化;

而深拷贝指向了新的地址,并拷贝了一份新的a和b,与之前c中的a和b列表不同,当a或b发生变化时,d不会发生了变化。

 

总结

  • 如果用copy.copy()copy.deepcopy()对一个全部都是不可变类型进行拷贝,那么它们的结果相同都是指向。

  • 如果拷贝的是一个拥有可变类型的数据时,即使时元组是最顶层(元组中含有可变数据),那么copy.deepcopy()依然是深拷贝,copy.copy()依然是指向。

 

其他拷贝方式

列表的切片

a = [11, 22]
b = [33, 44]
c = [a, b]
d = c[:]

print(c)
print(d)

print(id(c))
print(id(d))

print(id(c[0]))
print(id(d[0]))

a.append(55)
print(c)
print(d)  

结果

[[11, 22], [33, 44]]
[[11, 22], [33, 44]]

2105046387272
2105027365256

2105026105800
2105026105800

[[11, 22, 55], [33, 44]]
[[11, 22, 55], [33, 44]]

  可以看出,d指向新的地址,但其中的数据仍然指向a和b,所以列表的切片任然是浅拷贝。

字典的copy方法

d = dict(name="charles", age=27,num=[11,22])
co = d.copy()

print(d)
print(co)

print(id(d))
print(id(co))

print(id(d['name']))
print(id(co['name']))

d["num"].append(9)
print(d)
print(co)

d["sex"] = "M"
print(d)
print(co)

结果

{'name': 'charles', 'age': 27, 'num': [11, 22]}
{'name': 'charles', 'age': 27, 'num': [11, 22]}

1697702296936
1697704568136

1697702278704
1697702278704

{'name': 'charles', 'age': 27, 'num': [11, 22, 9]}
{'name': 'charles', 'age': 27, 'num': [11, 22, 9]}

{'name': 'charles', 'age': 27, 'num': [11, 22, 9], 'sex': 'M'}
{'name': 'charles', 'age': 27, 'num': [11, 22, 9]}

  字典中,键指向存放值的地址。

可以看出,字典的copy方法为浅拷贝。co指向一个新的地址来存储,存储的值指向d的最顶层数据存放地址,当对字典d中的值进行添加时,co中也发生变化;而当对字典d进行操作时,不会改变co的值。

函数传递参数

def test_nums(temp):
    temp.append(33)

nums = [11, 22]
test_nums(nums)

print(nums)  

结果

[11, 22, 33]

直接传递参数是浅拷贝,在函数对nums列表进行操作,改变了nums的值。

import copy

def test_nums(temp):
    temp.append(33)
    print("列表temp:%s"%temp)

nums = [11, 22]
test_nums(copy.deepcopy(nums))

print("列表nums:%s" %nums)  

结果

列表temp:[11, 22, 33]
列表nums:[11, 22]

将深拷贝的参数传递给函数后,函数的操作不会对nums列表有任何影响。

 

以上就是深拷贝与浅拷贝的一些总结了,如果您有什么疑问,请留言,欢迎大家一起讨论。  

  

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!