复制列表—赋值、深浅拷贝与切片拷贝

最近看了本书《你也能看得懂的Python算法书》,觉得不错,分享下学习心得~
这里是第一章~
Python中列表存储的方法和其他语言中的不太一样,列表中的元素在计算机的存储空间中占据一定的内存,而列表本身存储的是这些元素的存储地址,在调用列表元素的时候根据地址来调出它们原本的值。

1、赋值操作

如果直接给新的列表赋值,只是复制了原来列表存储的地址,所以元素本身并没有被复制成两份
赋值操作:

>>> numbers=[1,2,3,4,5,6]
>>> copylist=numbers
>>> id(numbers)
2644503419976
>>> id(copylist)
2644503419976

可以看出numbers和copylist共享一个ID地址
复制列表

2、Copy函数

Copy函数返回的是复制原列表中的元素后产生的一组新元素的存储地址,其被存储在新的列表中,这样,修改复制后的列表中的元素就不会影响原来的列表

>>> numbers=[1,2,3,4,5,6]
>>> id(numbers)
2644503419976
>>> copylist2=numbers.copy()
>>> id(copylist2)
2644503354968
>>>

可以看出copy函数给copylist2开辟了一个新ID
copy函数效果

3、切片复制

在这本书的第三章里,切片复制被说成是深拷贝!经调试,真不是!
在这里插入图片描述
来看看我的调试代码

a=[1,'two',[3,4]]
b=a[:]
print(a)
print()
a[0]=5
a[2][0]=6
print(a,b,sep='\n')

运行结果:

[1, 'two', [3, 4]]

[5, 'two', [6, 4]]
[1, 'two', [6, 4]]

列表b还是变了!
也就是说,对于嵌套列表,它和copy函数一样,是浅拷贝效果!
为什么呢?来看下ID

>>> id(a)
2468216504840
>>> id(b)
2468216504712
>>> id(a[2])
2468216504392
>>> id(b[2])
2468216504392
>>> id(a[0])
1497459456
>>> id(b[0])
1497459456
>>>

由上可以看出:id(a)!=id(b),id(a[0])=id(b[0]),id(a[1])=id(b[1])
所以,b对于a确实是开辟了新的ID,但对于列表里的元素,列表仅仅是引用了这些元素。
网上找了张很形象的图:
在这里插入图片描述

4、deepcopy函数

要想实现a和b其父对象和子对象均完全独立,可以使用deepcopy函数,即深拷贝。
在这里插入图片描述

import copy
a=[1,'two',[3,4]]
b=copy.deepcopy(a)
print('a[0]的内存地址:',id(a[0]))
print('b[0]的内存地址:',id(b[0]))
print('a[2]的内存地址:',id(a[2]))
print('b[2]的内存地址:',id(b[2]))
print()
a[0]=5
a[2][0]=6
print('子对象改变后:')
print('a[0]的内存地址:',id(a[0]))
print('b[0]的内存地址:',id(b[0]))
print('a[2]的内存地址:',id(a[2]))
print('b[2]的内存地址:',id(b[2]))

运行结果:

a[0]的内存地址: 1478322944
b[0]的内存地址: 1478322944
a[2]的内存地址: 2592844271304
b[2]的内存地址: 2592844271496

子对象改变后:
a[0]的内存地址: 1478323072
b[0]的内存地址: 1478322944
a[2]的内存地址: 2592844271304
b[2]的内存地址: 2592844271496

对比看出,深拷贝后a[2]和b[2]的内存地址是不一样的,且当改变a[0]时,a[0]的内存地址变,b[0]不变!父对象与子对象完全独立!
现在可以总结如下:
1)b = a: 赋值引用,a 和 b 都指向同一个对象。
2)b = a.copy(): 浅拷贝, a 和 b 是一个独立的对象,但他们的子对象还是指向统一对象(是引用)。
3)b = copy.deepcopy(a): 深度拷贝, a 和 b 完全拷贝了父对象及其子对象,两者是完全独立的。

接下来,我打算用浅拷贝来探索下列表~

主要会使用id() 函数来获取对象的内存地址。

a=[1,'two',[3,4]]
b=a.copy()
print('a[0]的内存地址:',id(a[0]))
print('b[0]的内存地址:',id(b[0]))
print('a[2]的内存地址:',id(a[2]))
print('b[2]的内存地址:',id(b[2]))
print()
a[0]=5
a[2][0]=6
print('子对象改变后:')
print('a[0]的内存地址:',id(a[0]))
print('b[0]的内存地址:',id(b[0]))
print('a[2]的内存地址:',id(a[2]))
print('b[2]的内存地址:',id(b[2]))

运行结果:

a[0]的内存地址: 1478322944
b[0]的内存地址: 1478322944
a[2]的内存地址: 2592843820168
b[2]的内存地址: 2592843820168

子对象改变后:
a[0]的内存地址: 1478323072
b[0]的内存地址: 1478322944
a[2]的内存地址: 2592843820168
b[2]的内存地址: 2592843820168

在a=[1,‘two’,[3,4]]中,a[0]存储的对象为不可变对象即数值类型int ,对于不可变对象,变量对应内存的值不允许被改变。当变量要改变时(即进行a[0]=5操作时),实际上是把原来的值复制一份后再改变,开辟一个新的地址,所以子对象改变前后,a[0]的内存地址变了;
a[2]存储对象为可变对象list,可变对象由于所指对象可以被修改,所以无需复制一份之后再改变,直接原地改变,所以不会开辟新的内存,改变前后id不变。

最后来道面试题:求a,b,c,d,e的值

import copy
a=[1,'two',[3,4]]
b=a
c=copy.copy(a)
d=copy.deepcopy(a)
e=a[:]
print('修改前原列表',a)
print()
a[0]=5
a[2][0]=6
print('原列表      ',a)
print('赋值列表    ',b)
print('浅拷贝列表  ',c)
print('深拷贝列表  ',d)
print('切片拷贝列表',e)

结果:

修改前原列表 [1, 'two', [3, 4]]

原列表       [5, 'two', [6, 4]]
赋值列表     [5, 'two', [6, 4]]
浅拷贝列表   [1, 'two', [6, 4]]
深拷贝列表   [1, 'two', [3, 4]]
切片拷贝列表 [1, 'two', [6, 4]]

本章其他函数整理:

功能函数语法/格式效果/返回值
排序sortList.sort()对List进行永久性从小到大排序
reserveList.reserve()对List永久性反转
最值maxmax(List)返回表中最大值(数值型)
minmin(List)返回表中最小值(数值型)
统计countList.count(item)返回表中item元素出现的个数(数值型)
索引indexList.index(item)返回表中item元素位置索引(数值型)
清空clearList.clear()返回空列表

参考文献:
[1]王硕.你也能看得懂的 Python算法书[M].北京:电子工业出版社,2018.30-35
[2]Python中可变对象和不可变对象 - 尼古拉斯特仑苏 - 博客园 https://www.cnblogs.com/suxianglun/p/9013021.html
[3]Python 直接赋值、浅拷贝和深度拷贝解析 | 菜鸟教程 https://www.runoob.com/w3cnote/python-understanding-dict-copy-shallow-or-deep.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值