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