Python 深浅拷贝 - 图文并茂从底层内存地址解说Python 深浅拷贝(shallow copy and deep copy)、赋值操作、浅拷贝、深拷贝

前言

熟悉C语言的同学都知道,申请一个变量,其实是通过malloc()函数申请了一段相应数据类型的内存空间,并将该内存空间的内存地址赋值给该变量,此后无论是读数据还是写数据都通过内存地址来访问。
本文较长,如感不适,可直接看结论和配图

探索Python 中 变量的赋值不可变数据类型的地址变化过程

本实验分两个步骤:
步骤一:变量赋值并查看赋值后的内存地址
步骤二:修改其中一个变量的值并观察更改后的内存地址变化

步骤一:变量赋值并查看赋值后的内存地址

a = 1
b = a

print(a)
print(b)

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

以下为运行输出内容

1
1
2032924592
2032924592

步骤二:修改其中一个变量的值并观察更改后的内存地址变化

修改变量a的值,观察地址变化

a = 1
b = a

print(a)
print(b)

print(id(a))
print(id(b))
######################################################################
a = 3		#<--------------------修改a的值

print(a)
print(b)

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

1
1
2032924592
2032924592
3
1
2032924624     #<-------------------------观察发现此处地址内存变化
2032924592

修改b的值,再观察内存变化

a = 1
b = a

print(a)
print(b)

print(id(a))
print(id(b))
######################################################################
b = 3		#<-----------------------------修改b的值

print(a)
print(b)

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

1
1
2032924592
2032924592
1
3
2032924592
2032924624	 #<-------------------------观察发现此处地址内存变化

图示与结论:

初始情况 下的内存指向值变化后的内存指向

将一个不可变数据类型的值赋值给变量后,再赋值给另外一个变量时,其实内部是两个变量同时指向同一个内存地址,但是一旦值发生改变,则新开辟一个内存空间后,存储新值 ,然后将相应的变量指向这个新的内存地址。

探索Python 中 变量的赋值可变数据类型的地址变化过程

本实验分两个步骤:
步骤一:变量赋值为可变类型的对象,并查看赋值后的内存地址与内容
步骤二:修改其中一个变量的值并观察更改后的内存地址变化

步骤一:变量赋值并查看赋值后的内存地址

a = [[1,2],3,4,"python"]
b = a

print("初始情况".center(50,"*"))

print("a的内容:",a)
print("b的内容:",b)
print("a的内存地址:",id(a))
print("b的内存地址:",id(b))

print("a-0-0的地址",id(a[0][0]))
print("a-0-1的地址",id(a[0][1]))
print("a-1的地址",id(a[1]))
print("a-2的地址",id(a[2]))
print("a-3的地址",id(a[3]))

print("分割符,上面为a的,下面为b的".center(50,"*"))

print("b-0-0的地址",id(b[0][0]))
print("b-0-1的地址",id(b[0][1]))
print("b-1的地址",id(b[1]))
print("b-2的地址",id(b[2]))
print("b-3的地址",id(b[3]))
***********************初始情况***********************
a的内容: [[1, 2], 3, 4, 'python']
b的内容: [[1, 2], 3, 4, 'python']
a的内存地址: 57636200
b的内存地址: 57636200
a-0-0的地址 2031613872
a-0-1的地址 2031613888
a-1的地址 2031613904
a-2的地址 2031613920
a-3的地址 58865344
*****************分割符,上面为a的,下面为b的******************
b-0-0的地址 2031613872
b-0-1的地址 2031613888
b-1的地址 2031613904
b-2的地址 2031613920
b-3的地址 58865344

步骤二:修改其中一个变量的值并观察更改后的内存地址变化

print("修改数据".center(50,"*"))
b[0][1] = 10
b[1] = 8

print("a的内容:",a)
print("b的内容:",b)
print("a的内存地址:",id(a))
print("b的内存地址:",id(b))

print("a-0-0的地址",id(a[0][0]))
print("a-0-1的地址",id(a[0][1]))
print("a-1的地址",id(a[1]))
print("a-2的地址",id(a[2]))
print("a-3的地址",id(a[3]))

print("分割符".center(50,"*"))

print("b-0-0的地址",id(b[0][0]))
print("b-0-1的地址",id(b[0][1]))
print("b-1的地址",id(b[1]))
print("b-2的地址",id(b[2]))
print("b-3的地址",id(b[3]))
***********************修改数据***********************
a的内容: [[1, 10], 8, 4, 'python']
b的内容: [[1, 10], 8, 4, 'python']
a的内存地址: 57636200
b的内存地址: 57636200
a-0-0的地址 2031613872
a-0-1的地址 2031614016
a-1的地址 2031613984
a-2的地址 2031613920
a-3的地址 58865344
***********************分割符************************
b-0-0的地址 2031613872
b-0-1的地址 2031614016
b-1的地址 2031613984
b-2的地址 2031613920
b-3的地址 58865344


图示与结论:

数据更改之前

变更数据之后

内存地址没有发生变化,但是数据都发生了变化,在这种可变的数据类型中,变量所指向的内存地址是这个可变数据类型的另一个块儿的内存地址,而里面的值是其它的内存地址,当改变里面的值的数据时,并没有修改变量所指向的内存地址
1. 从步骤一可以看出,a和b指向的是同一段内存地址,且里面的值也是指向的同一个内存地址
2. 从步骤二可以看出,在改变数据后列表的内存地址没有改变
3. 从步骤二可以看出,在改变数据后改变的数据的内存地址发生了改变
4. 从步骤二可以看出,在改变数据后没有改变数据的内存地址没变

探索Python 中 浅拷贝的地址变化过程

本实验分二个步骤:
步骤一:变量赋值为可变类型的对象,并查看赋值后的内存地址与内容
步骤二:修改其中一个变量和一个子列表的值并观察更改后的内存地址变化

步骤一:变量赋值并查看赋值后的内存地址

a = [[1,2],3,4,"python"]
b = a.copy()#通过copy()方法进行拷贝的是浅拷贝,另外一个b=a[:] 也是浅拷贝

print("初始情况".center(50,"*"))

print("a的内容:",a)
print("b的内容:",b)
print("a的内存地址:",id(a))
print("b的内存地址:",id(b))

print("a-0的地址",id(a[0]))
print("a-0-0的地址",id(a[0][0]))
print("a-0-1的地址",id(a[0][1]))
print("a-1的地址",id(a[1]))
print("a-2的地址",id(a[2]))
print("a-3的地址",id(a[3]))

print("分割符,上面为a的,下面为b的".center(50,"*"))

print("b-0的地址",id(b[0]))
print("b-0-0的地址",id(b[0][0]))
print("b-0-1的地址",id(b[0][1]))
print("b-1的地址",id(b[1]))
print("b-2的地址",id(b[2]))
print("b-3的地址",id(b[3]))

***********************初始情况***********************
a的内容: [[1, 2], 3, 4, 'python']
b的内容: [[1, 2], 3, 4, 'python']
a的内存地址: 20411752
b的内存地址: 20412456
a-0的地址 46390920
a-0-0的地址 2031613872
a-0-1的地址 2031613888
a-1的地址 2031613904
a-2的地址 2031613920
a-3的地址 58406560
*****************分割符,上面为a的,下面为b的******************
b-0的地址 46390920
b-0-0的地址 2031613872
b-0-1的地址 2031613888
b-1的地址 2031613904
b-2的地址 2031613920
b-3的地址 58406560

步骤二:修改其中一个变量的值并观察更改后的内存地址变化

print("修改数据".center(50,"*"))
b[0][1] = 10
b[1] = 8

print("a的内容:",a)
print("b的内容:",b)
print("a的内存地址:",id(a))
print("b的内存地址:",id(b))

print("a-0的地址",id(a[0]))
print("a-0-0的地址",id(a[0][0]))
print("a-0-1的地址",id(a[0][1]))
print("a-1的地址",id(a[1]))
print("a-2的地址",id(a[2]))
print("a-3的地址",id(a[3]))

print("分割符".center(50,"*"))

print("b-0的地址",id(b[0]))
print("b-0-0的地址",id(b[0][0]))
print("b-0-1的地址",id(b[0][1]))
print("b-1的地址",id(b[1]))
print("b-2的地址",id(b[2]))
print("b-3的地址",id(b[3]))



***********************修改数据***********************
a的内容: [[1, 10], 3, 4, 'python']
b的内容: [[1, 10], 8, 4, 'python']
a的内存地址: 20411752
b的内存地址: 20412456
a-0的地址 46390920
a-0-0的地址 2031613872
a-0-1的地址 2031614016
a-1的地址 2031613904
a-2的地址 2031613920
a-3的地址 58406560
***********************分割符************************
b-0的地址 46390920
b-0-0的地址 2031613872
b-0-1的地址 2031614016
b-1的地址 2031613984
b-2的地址 2031613920
b-3的地址 58406560

图示与结论:

在这里插入图片描述在这里插入图片描述

1. 从步骤一中可以直接看出,a与b的内容虽然一样,但是内存地址已经不一样了,所以已经新开辟了一段内存空间,然后b指向了过去
2. 从步骤二中可以看出,数据发生改变后,a与b本身的内存地址并没有改变
3. 从步骤二中还可以看出,子列表的元素的内存地址一样,但是其它未改变的元素的内存地址还是一样的。已经发生改变的元素的地址不一样。
4. 从步骤二中可以看出.在此浅拷贝中,如果改变第一层列表的值后,改变哪个列表的值,该列表将对应的内存地址改成新的值所对应的内存地址,改变的值的内存地址会发生变化
5.而如果改变第二层就是子列表的值后,两个变量的值都发生了变化,且内存地址也改变,说明是新开辟的一段内存空间,然后将指向全部改到了新指向

特别说明现象

1. 修改最外一层列表时,修改a或者b的值,则只影响自己的值,不会影响另外一个变量
2.修改第二层子列表的值时,无论修改a或者修改b的,都会影响对方的值

原理剖析

在变量a或者变量b所指向的内存地址空间只包含了最外一层列表,也就是“数组”的内存地址,其中包含下标从0-3的地址空间,而a[0]或者b[0]这个下标仅指向了第二层列表的列表的内存地址空间,而内部的元素的地址空间在a与b中并没有记录,也没有相应的下标号,所以在改变下标0,1.2.3的值的时候会指向新的内存地址,而改变子列表a[0]下面再下一层的值时,子列表本身的内存地址并没有改变,而只是改变了值的内存地址。所以简单的说,浅拷贝只拷贝第一层下标所指向的内存地址。

探索Python 中 深拷贝的地址变化过程

本实验分二个步骤:
步骤一:变量赋值为可变类型的对象,并查看赋值后的内存地址与内容和深拷贝的地址与内容
步骤二:修改其中一个变量和一个子列表的值并观察更改后的内存地址变化

步骤一:变量赋值为可变类型的对象,并查看赋值后的内存地址与内容和深拷贝的地址与内容

import copy

a = [[1,2],3,4,"python"]
b = copy.deepcopy(a)

print("初始情况".center(50,"*"))

print("a的内容:",a)
print("b的内容:",b)
print("a的内存地址:",id(a))
print("b的内存地址:",id(b))

print("a-0的地址",id(a[0]))
print("a-0-0的地址",id(a[0][0]))
print("a-0-1的地址",id(a[0][1]))
print("a-1的地址",id(a[1]))
print("a-2的地址",id(a[2]))
print("a-3的地址",id(a[3]))

print("分割符,上面为a的,下面为b的".center(50,"*"))

print("b-0的地址",id(b[0]))
print("b-0-0的地址",id(b[0][0]))
print("b-0-1的地址",id(b[0][1]))
print("b-1的地址",id(b[1]))
print("b-2的地址",id(b[2]))
print("b-3的地址",id(b[3]))

***********************初始情况***********************
a的内容: [[1, 2], 3, 4, 'python']
b的内容: [[1, 2], 3, 4, 'python']
a的内存地址: 59242216
b的内存地址: 59242024
a-0的地址 58030472
a-0-0的地址 2031613872
a-0-1的地址 2031613888
a-1的地址 2031613904
a-2的地址 2031613920
a-3的地址 59282176
*****************分割符,上面为a的,下面为b的******************
b-0的地址 59242248
b-0-0的地址 2031613872
b-0-1的地址 2031613888
b-1的地址 2031613904
b-2的地址 2031613920
b-3的地址 59282176

步骤二:修改其中一个变量和一个子列表的值并观察更改后的内存地址变化

print("修改数据".center(50,"*"))
b[0][1] = 10
b[1] = 8

print("a的内容:",a)
print("b的内容:",b)
print("a的内存地址:",id(a))
print("b的内存地址:",id(b))

print("a-0的地址",id(a[0]))
print("a-0-0的地址",id(a[0][0]))
print("a-0-1的地址",id(a[0][1]))
print("a-1的地址",id(a[1]))
print("a-2的地址",id(a[2]))
print("a-3的地址",id(a[3]))

print("分割符".center(50,"*"))

print("b-0的地址",id(b[0]))
print("b-0-0的地址",id(b[0][0]))
print("b-0-1的地址",id(b[0][1]))
print("b-1的地址",id(b[1]))
print("b-2的地址",id(b[2]))
print("b-3的地址",id(b[3]))


***********************修改数据***********************
a的内容: [[1, 2], 3, 4, 'python']
b的内容: [[1, 10], 8, 4, 'python']
a的内存地址: 59242216
b的内存地址: 59242024
a-0的地址 58030472
a-0-0的地址 2031613872
a-0-1的地址 2031613888
a-1的地址 2031613904
a-2的地址 2031613920
a-3的地址 59282176
***********************分割符************************
b-0的地址 59242248
b-0-0的地址 2031613872
b-0-1的地址 2031614016
b-1的地址 2031613984
b-2的地址 2031613920
b-3的地址 59282176

图示与结论

在这里插入图片描述
在这里插入图片描述

1.从步骤一可以看出,数据未发生改变前,a与b的内存地址不一样,但是指向的值的内存地址是一样的
2.从步骤一可以看出,除了a与b指向的列表的内存地址不一样外,子列表的内存地址也不一样,但是子列表内的值的内存地址是一样的
3.从步骤二可以看出,修改b变量所指向的列表时,a的值与内存地址没有发生任何改变
1.从步骤二可以看出,因为修改了b的列表的值,所以未改变的值的内存地址没有改变,而改变值的内存地址发生了改变

特别说明现象

1. 无论修改外层列表,还是内层列表,改的列表的相应的值与内存地址都发生改变
2.在值不变的情况下,具体值指向的内存地址空间还是一样的
3.在深层拷贝的情况下,子列表改值后,会开辟一个新空间存新值,然后修改内存地址指向

附录:官方文档关于"copy — Shallow and deep copy operations"

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值