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

  • Post author:
  • Post category:其他


最近看了本书《你也能看得懂的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]]

本章其他函数整理:

功能 函数 语法/格式 效果/返回值
排序 sort List.sort() 对List进行永久性从小到大排序
reserve List.reserve() 对List永久性反转
最值 max max(List) 返回表中最大值(数值型)
min min(List) 返回表中最小值(数值型)
统计 count List.count(item) 返回表中item元素出现的个数(数值型)
索引 index List.index(item) 返回表中item元素位置索引(数值型)
清空 clear List.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



版权声明:本文为weixin_43224710原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。