Python浅拷贝与深拷贝的区别和理解

1 数据类型是否可变

Python 3 中,有6个标准的数据类型,他们又分为可变和不可变。

1.1 不可变数据(3个)
  • Number(数字)
  • String(字符串)
  • Tuple(元组)
1.2 可变数据(3个)
  • List(列表)
  • Dictionary(字典)
  • Set(集合)

2 浅拷贝和深拷贝

2.1 浅拷贝

1、对于不可变类型 Number String Tuple,浅拷贝仅仅是地址指向,不会开辟新空间。
2、对于可变类型 List、Dictionary、Set,浅拷贝会开辟新的空间地址(仅仅是最顶层开辟了新的空间,里层的元素地址还是一样的),进行浅拷贝
3、浅拷贝后,改变原始对象中为可变类型的元素的值,会同时影响拷贝对象的;改变原始对象中为不可变类型的元素的值,只有原始类型受影响。(操作拷贝对象对原始对象的也是同理)

2.2 深拷贝

1、浅拷贝,除了顶层拷贝,还对子元素也进行了拷贝(本质上递归浅拷贝)
2、经过深拷贝后,原始对象和拷贝对象所有的元素地址都没有相同的了
3、可以用分片表达式进行深拷贝
4、字典的copy方法可以拷贝一个字典


3 可变类型和不可变类型在浅拷贝中的区别

3.1 copy模块里面的copy方法实现
3.1.1 地址
import copy

# 不可变类型 Number String Tuple

num1 = 2
num2 = copy.copy(num1)
print("num1:",num1,str(id(num1)))
print("num2:",num2,str(id(num1)))
# result:
# num1: 2 1864261824
# num2: 2 1864261824
# num1和num2的地址都相同

str1 = "hello world"
str2 = copy.copy(str1)
print("str1:",str1,str(id(str1)))
print("str2:",str2,str(id(str2)))
# result:
# str1: hello world 2190089916656
# str2: hello world 2190089916656
# str1和str2的地址都相同

tup1 = ("scott", 20)
tup2 = copy.copy(tup1)
print("tup1:",tup1,str(id(tup1)))
print("tup2:",tup2,str(id(tup2)))
# result:
# tup1: ('scott', 20) 2190089333960
# tup2: ('scott', 20) 2190089333960
# tup1和tup2的地址都相同

##########################################################
# 可变类型 List Dict Set

list1 = [1,2]
list2 = copy.copy(list1)
print("list1:",list1,str(id(list1)))
print("list2:",list2,str(id(list2)))
# result:
# list1: [1, 2] 2190090206280
# list2: [1, 2] 2190090345992
# list1和list2的地址不相同

dic1 = {"a":1,"b":2}
dic2 = copy.copy(dic1)
print("dic1:",dic1,str(id(dic1)))
print("dic2:",dic2,str(id(dic2)))
# result:
# dic1: {'a': 1, 'b': 2} 2190089946384
# dic2: {'a': 1, 'b': 2} 2190090331984
# dic1和dic2的地址不相同

set1 = {1,2,"AA","BB"}
set2 = copy.copy(set1)
print("set1:",set1,str(id(set1)))
print("set2:",set2,str(id(set2)))
# result:
# set1: {1, 2, 'AA', 'BB'} 2190090061864
# set2: {1, 2, 'AA', 'BB'} 2190090063656
# set1和set2的地址不相同

因此可以看出:
对于不可变类型,浅copy仅仅是地址指向,不会开辟新空间拷贝值
对于可变类型,浅copy会开辟新的空间地址(仅仅是最顶层开辟了新的空间),进行浅拷贝

num1: 2 1864261824
num2: 2 1864261824
str1: hello world 2190090055728
str2: hello world 2190090055728
tup1: ('scott', 20) 2190090465480
tup2: ('scott', 20) 2190090465480
list1: [1, 2] 2190089645512
list2: [1, 2] 2190090475080
dic1: {'a': 1, 'b': 2} 2190089814448
dic2: {'a': 1, 'b': 2} 2190090212984
set1: {1, 2, 'AA', 'BB'} 2190000480776
set2: {1, 2, 'AA', 'BB'} 2190090061640
3.1.2 内容

浅拷贝对可变类型和不可变类型修改后的影响。

import copy

lis1 = [1, 2]
lis2 = [11, 22]
num = 33
a = [lis1, lis2,num]
# 浅拷贝,创建出一个对象,并把旧对象元素的 引用地址 拷贝到新对象当中。
# 也就是说,两个对象里面的元素通过浅拷贝指向的还是同一个地址
a2 = copy.copy(a)
print(a)
print(a2)

# 修改内容
lis1[0] = 1000 # 此处修改,会使得 a 和 a2的第0个元素的值都发生改变,因为lis1是List,是可变对象
a[2] = 666 # 此处修改,只会a的num的值,因为不可变对象一旦重新复制,地址就会发生改变。
num = 777 # 此处不会改变 a 和 a2的值,因为相当于 777 复制给一个全新的地址,这个num跟其他num已经没关系了

print(a)
print(a2)
print("id a:"+str(id(a)))
print("id a[0]:"+str(id(a[0])))
print("id a[1]:"+str(id(a[1])))
print("id a[2]:"+str(id(a[2])))
print("-------------------------")
print("id a2:"+str(id(a2)))
print("id a2[0]:"+str(id(a2[0])))
print("id a2[1]:"+str(id(a2[1])))
print("id a2[2]:"+str(id(a2[2])))

结果:

[[1, 2], [11, 22], 33]
[[1, 2], [11, 22], 33]
[[1000, 2], [11, 22], 666]
[[1000, 2], [11, 22], 33]
id a:2190089925256
id a[0]:2190090145224
id a[1]:2190089917320
id a[2]:2190089840368
-------------------------
id a2:2190089869832
id a2[0]:2190090145224
id a2[1]:2190089917320
id a2[2]:1864262816

可以看出:
改动a中的可变类型,会影响a2,改变a2同理影响a。
改动a中的不可变类型,只有a自身会改变,a2不受影响。

3.2 字典的copy方法拷贝新字典
dict1 =  {'a':'aaa','b':'bbb','num':[1,2,3,4]}
dict2 = dict1          # 默认浅拷贝: 引用对象
dict3 = dict1.copy()   # 浅拷贝:深拷贝父对象(一级目录),子对象(二级目录)不拷贝,还是引用
# 修改内容
#此处修改,会使得dict1、dict2和dict3的num中的value发生改变,因为num中的value是List,是可变对象
dict1['num'].remove(1)
dict1['num'].append(666)
# 此处修改,只有dict2的a中的value改变,因为a中的value是string,为不可变对象
dict1['a']='hahaha'
# 此处修改,会使得dict1的b中的value改变
#虽然把dict1赋值给dict2,但是对dict2的操作却修改了dict1的值, 
#也就是dict1和dict2共同管理了同一个对象,创建dict2时并没有创建新的对象
#但是dict3不会改变,因为a中的value是string,为不可变对象
dict2["b"] = "aaa"
dict3["d"] = "ddd"

print(dict1)
print(dict2)
print(dict3)
print("dict1:" + str(id(dict1)))
print("dict2:" + str(id(dict2)))
print("dict3:" + str(id(dict3)))

result:

{'a': 'hahaha', 'b': 'aaa', 'num': [2, 3, 4, 666]}
{'a': 'hahaha', 'b': 'aaa', 'num': [2, 3, 4, 666]}
{'a': 'aaa', 'b': 'bbb', 'num': [2, 3, 4, 666], 'd': 'ddd'}
dict1:2190089811720
dict2:2190089811720
dict3:2190089810856

可以看出:
1.实例中 dict2 其实是 dict1 的引用(别名),所以输出结果都是一致的
2.dict3 父对象进行了深拷贝,不会随dict1 修改而修改,子对象是浅拷贝所以随 dict1 的修改而修改。


4 可变类型和不可变类型在深拷贝中的区别

4.1 copy模块里面的copy方法实现
4.1.1 地址
import copy

# 不可变类型 Number String Tuple

num1 = 1
num2 = copy.deepcopy(num1) # 深拷贝

print("num1:",num1,str(id(num1)))
print("num2:",num2,str(id(num1)))
# num1和num2的地址都相同


str1 = "hello world"
str2 = copy.deepcopy(str1)  # 深拷贝
print("str1:",str1,str(id(str1)))
print("str2:",str2,str(id(str2)))
# str1和str2的地址都相同

tup1 = ("scott", 20)
tup2 = copy.deepcopy(tup1) # 深拷贝
print("tup1:",tup1,str(id(tup1)))
print("tup2:",tup2,str(id(tup2)))
# tup1和tup2的地址都相同

##########################################################
# 不可变类型 List Dict Set
# 对于可变类型 List、Dictionary、Set,深拷贝会开辟新的空间地址,进行拷贝


list1 = [1,2]
list2 = copy.deepcopy(list1) # 深拷贝
print("list1:",list1,str(id(list1)))
print("list2:",list2,str(id(list2)))
# list1和list2的地址不相同


dic1 = {"a":1,"b":2}
dic2 = copy.deepcopy(dic1) # 深拷贝
print("dic1:",dic1,str(id(dic1)))
print("dic2:",dic2,str(id(dic2)))
# dic1和dic2的地址不相同

set1 = {1,2,"AA","BB"}
set2 = copy.deepcopy(set1) # 深拷贝
print("set1:",set1,str(id(set1)))
print("set2:",set2,str(id(set2)))
# set1和set2的地址不相同

输出:

num1: 1 1864261792
num2: 1 1864261792
str1: hello world 2190063283568
str2: hello world 2190063283568
tup1: ('scott', 20) 2190089254216
tup2: ('scott', 20) 2190089254216
list1: [1, 2] 2190089852872
list2: [1, 2] 2190090000776
dic1: {'a': 1, 'b': 2} 2190089890480
dic2: {'a': 1, 'b': 2} 2190089890840
set1: {1, 2, 'AA', 'BB'} 2190090063432
set2: {1, 2, 'AA', 'BB'} 2190000480776

可以看出,深拷贝6种基本类型和浅拷贝几乎一致。
因为对于顶层的操作,深浅拷贝无异。

4.1.2 内容
import copy

lis1 = [1, 2]
lis2 = [11, 22]
num = 33
a = [lis1, lis2,num]

# 浅拷贝,除了顶层拷贝,还对子元素也进行了拷贝(本质上递归浅拷贝)
# 经过深拷贝后,原始对象和拷贝对象所有的元素地址都没有相同的了
# 深拷贝的会对子元素也进行拷贝
a2 = copy.deepcopy(a) # copy.deepcopy 深拷贝
print(a)
print(a2)
a[1] = [1000,2000]
a2[2] = [233,2333]

print(a)
print(a2)
print("id a:"+str(id(a)))
print("id a[0]:"+str(id(a[0])))
print("id a[1]:"+str(id(a[1])))
print("id a[2]:"+str(id(a[2])))
print("-------------------------")
print("id a2:"+str(id(a2)))
print("id a2[0]:"+str(id(a2[0])))
print("id a2[1]:"+str(id(a2[1])))
print("id a2[2]:"+str(id(a2[2])))

result:

[[1, 2], [11, 22], 33]
[[1, 2], [11, 22], 33]
[[1, 2], [1000, 2000], 33]
[[1, 2], [11, 22], [233, 2333]]
id a:2190090345544
id a[0]:2190089998920
id a[1]:2190089917320
id a[2]:1864262816
-------------------------
id a2:2190089999880
id a2[0]:2190089851016
id a2[1]:2190090001928
id a2[2]:2190089869832

在之前的浅拷贝中,子元素是不会开辟新空间做拷贝的。
而在深拷贝中,子元素也进行了拷贝。

4.2 分片表达式进行深拷贝

除了copy模块的中的copy和deepcopy,还有分片表达式进行深拷贝

# 分片表达式拷贝
lis1 = [11, 22]
lis2 = [333, 444]
num = 6

c = [lis1, lis2, num]
# 浅拷贝
c2 = c
# 分片表达式深拷贝
c3 = c[:]
print(c)
print(c2)
print(c3)

# 修改内容
c[1] = [1000,2000]
c2[2] = [233,2333]

print(c)
print(c2)
print(c3)
print("c:"+str(id(c)))
print("c[0]:"+str(id(c[0])))
print("c[1]:"+str(id(c[1])))
print("c[2]:"+str(id(c[2])))
print("-"*30)
print("c2:"+str(id(c2)))
print("c2[0]:"+str(id(c2[0])))
print("c2[1]:"+str(id(c2[1])))
print("c2[2]:"+str(id(c2[2])))
print("-"*30)
print("c3:"+str(id(c3)))
print("c3[0]:"+str(id(c3[0])))
print("c3[1]:"+str(id(c3[1])))
print("c3[2]:"+str(id(c3[2])))

result:

[[11, 22], [333, 444], 6]
[[11, 22], [333, 444], 6]
[[11, 22], [333, 444], 6]
[[11, 22], [1000, 2000], [233, 2333]]
[[11, 22], [1000, 2000], [233, 2333]]
[[11, 22], [333, 444], 6]
c:2190089642056
c[0]:2190089642120
c[1]:2190090345480
c[2]:2190090345096
------------------------------
c2:2190089642056
c2[0]:2190089642120
c2[1]:2190090345480
c2[2]:2190090345096
------------------------------
c3:2190089645640
c3[0]:2190089642120
c3[1]:2190089643592
c3[2]:1864261952
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值