20220714 Python append()函数浅拷贝与深拷贝
copy
-- 浅层 (shallow) 和深层 (deep) 复制操作:https://docs.python.org/zh-cn/3/library/copy.htmlPython避坑:append()函数与浅拷贝:https://www.jianshu.com/p/6e77e8073ffc
Python 浅拷贝和深拷贝:https://www.programiz.com/python-programming/shallow-deep-copy
我遇到了一个问题
我写了下面一段代码,大致是下面这个样子。最后列表里面的元素全都变成一模一样的了。
>>> students_list = []
>>> student = {}
>>> student["name"] = "zhangsan"
>>> student["age"] = 18
>>> students_list.append(student)
>>> student["name"] = "zhaosi"
>>> student["age"] = 25
>>> students_list.append(student)
>>> print(students_list)
[{'name': 'zhaosi', 'age': 25}, {'name': 'zhaosi', 'age': 25}]
原因:append()方法只是将字典的地址存到list中,而键赋值的方式就是修改地址,所以才导致覆盖的问题
>>> students_list = []
>>> student = {}
>>> student["name"] = "zhaosi"
>>> student["age"] = 25
>>> students_list.append(student)
>>> id(student)
140240891529024
>>> id(students_list[0])
140240891529024
解决:上面问题,使用copy()或者deepcopy()都可以解决
>>> students_list = []
>>> student = {}
>>> student["name"] = "zhangsan"
>>> student["age"] = 18
>>> students_list.append(student)
>>> student["name"] = "zhaosi"
>>> students_list = []
>>> student = {}
>>> student["name"] = "zhangsan"
>>> student["age"] = 18
>>> students_list.append(student.copy())
>>> student["name"] = "zhaosi"
>>> student["age"] = 25
>>> students_list.append(student.copy())
>>> print(students_list)
[{'name': 'zhangsan', 'age': 18}, {'name': 'zhaosi', 'age': 25}
何为浅拷贝,深拷贝
我们常说的深浅拷贝,其实就是传值与引用的区别:
- 深拷贝 是指使用一块新的与原对象相同大小的内存,将需要拷贝的对象的所有值都拷贝到新的内存位置中,拷贝出来的对象与原对象互相独立。 使用深拷贝赋值,传的是值。
- 浅拷贝 是指使用原对象的引用作为拷贝的对象,而不使用新的内存存放拷贝出来的对象,拷贝的对象与原对象共用同一块内存,所以对其中任一个做修改,另一个也会随之改变。 使用浅拷贝赋值,传的是引用。
Python中对象的赋值
Python中很多地方都用到浅拷贝,这与C/C++默认的传参方式(除非声明是引用,否则默认传值)是截然不同的。
在Python中,对象的赋值是浅拷贝,当把一个对象赋值给另一个变量的时候,Python只是拷贝了这个对象的引用。
使用Python标准库一些函数,如果不注意它浅拷贝的特性,很容易掉进坑里,出现莫名其妙的bug。
append()函数
给Pyhton的列表尾部追加元素,我们通常会用到append()函数,但是要注意 append()函数使用的是浅拷贝。
正如我开头所举得例子一样。
什么时候需要用到深拷贝
当列表里面嵌套列表的时候,就需要用到深拷贝。
经典案例:运算符 vs 浅拷贝 vs 深拷贝
来自:https://www.programiz.com/python-programming/shallow-deep-copy
示例一:=
运算符进行复制
old_list = [[1, 2, 3], [4, 5, 6], [7, 8, 'a']]
new_list = old_list
new_list[2][2] = 9
print('Old List:', old_list)
print('ID of Old List:', id(old_list))
print('New List:', new_list)
print('ID of New List:', id(new_list))
输出结果
Old List: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
ID of Old List: 140425275713928
New List: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
ID of New List: 140425275713928
从输出中可以看出两个变量旧列表和新列表共享相同的 id 即140673303268168
。
所以,如果你想修改任何值新列表或者旧列表,变化在两者中都是可见的。
本质上,有时您可能希望保持原始值不变而只修改新值,反之亦然。在 Python 中,有两种创建副本的方法:
- 浅拷贝
- 深拷贝
为了使这些复制工作,我们使用copy
模块。
示例二:浅拷贝创建副本
import copy
old_list = [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
new_list = copy.copy(old_list)
old_list.append([4, 4, 4])
new_list.append([5, 5, 5])
print("Old list:", old_list)
print("New list:", new_list)
输出结果:
Old list: [[1, 1, 1], [2, 2, 2], [3, 3, 3], [4, 4, 4]]
New list: [[1, 1, 1], [2, 2, 2], [3, 3, 3], [5, 5, 5]]
上面程序中,我们对old_list
创建了一个浅拷贝new_list
.然后分别在old_list
和new_list
里面追加新的元素,可以看到互相是独立的,不受影响。
示例三:使用浅拷贝添加新的嵌套对象
import copy
old_list = [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
new_list = copy.copy(old_list)
old_list[1][1] = 'AA'
print("Old list:", old_list)
print("New list:", new_list)
输出结果:
Old list: [[1, 1, 1], [2, 'AA', 2], [3, 3, 3]]
New list: [[1, 1, 1], [2, 'AA', 2], [3, 3, 3]]
上面程序中,我们对old_list
创建了一个浅拷贝new_list
.然后对old_list
里面嵌套的列表元素进行了修改,发现new_list
也跟着一块改变了。这是因为, 两个列表共享相同嵌套对象的引用。
示例四:使用深拷贝添加新的嵌套引用
import copy
old_list = [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
new_list = copy.deepcopy(old_list)
old_list[1][0] = 'BB'
print("Old list:", old_list)
print("New list:", new_list)
输出结果:
Old list: [[1, 1, 1], ['BB', 2, 2], [3, 3, 3]]
New list: [[1, 1, 1], [2, 2, 2], [3, 3, 3]]
可以看到,使用了深拷贝之后。修改old_list
的元素不再影响new_list
.这意味着 old_list
和new_list
是独立的。这是因为, old_list
被递归复制,对于所有嵌套对象都是如此。