在 Python 编程领域,列表是一种极为常用的数据结构,用于存储多个元素的有序集合。当涉及到对列表进行复制操作时,浅拷贝和深拷贝是两种重要的概念与技术手段,它们在处理列表数据的过程中有着截然不同的行为和影响,深刻理解二者的差异与应用场景对于编写高效、准确且健壮的 Python 代码至关重要。
浅拷贝复制指向某个对象的地址(指针),而不复制对象本身,新对象和原对象共享同一内存。
深拷贝会额外创建一个新的对象,新对象跟原对象并不共享内存,修改新对象不会影响到原对象。
赋值其实就是引用了原对象。两者指向同一内存,两个对象是联动的,无论哪个对象发生改变都会影响到另一个。
使用=来复制一个列表,实际上不仅复制了其中的内容,也复制了其内存地址,即引用了原列表。使用id()方法查看内存地址也是一样的。修改其中一个列表,也会直接更改另一个列表。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
if __name__ == '__main__':
a = ["a", ["b", "c", "d"], ["e", "f"]]
# 直接复制,即引用列表
b = a
print(a)
print(b)
# 通过id()查看内存地址,为一样的
print(id(a), id(b))
b[0] = "g"
b[1][0] = "f"
print(a)
print(b)
输出:
['a', ['b', 'c', 'd'], ['e', 'f']]
['a', ['b', 'c', 'd'], ['e', 'f']]
2030264836936 2030264836936
['g', ['f', 'c', 'd'], ['e', 'f']]
['g', ['f', 'c', 'd'], ['e', 'f']]
Process finished with exit code 0
使用for循环进行拷贝,仅第一层为深拷贝,对其它层依然是浅拷贝。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
if __name__ == '__main__':
a = ["a", ["b", "c", "d"], ["e", "f"]]
b = []
# 使用for循环进行拷贝,仅第一层为深拷贝
for i in a:
b.append(i)
print(a)
print(b)
b[0] = "g"
b[1][0] = "f"
print(a)
print(b)
输出:
['a', ['b', 'c', 'd'], ['e', 'f']]
['a', ['b', 'c', 'd'], ['e', 'f']]
['a', ['f', 'c', 'd'], ['e', 'f']]
['g', ['f', 'c', 'd'], ['e', 'f']]
Process finished with exit code 0
使用切片方法进行拷贝,也仅对第一层为深拷贝,对其它层依然是浅拷贝。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
if __name__ == '__main__':
a = ["a", ["b", "c", "d"], ["e", "f"]]
# 使用切片进行拷贝,仅第一层为深拷贝
b = a[:]
print(a)
print(b)
b[0] = "g"
b[1][0] = "f"
print(a)
print(b)
输出:
['a', ['b', 'c', 'd'], ['e', 'f']]
['a', ['b', 'c', 'd'], ['e', 'f']]
['a', ['f', 'c', 'd'], ['e', 'f']]
['g', ['f', 'c', 'd'], ['e', 'f']]
Process finished with exit code 0
使用list.copy()方法进行拷贝,也仅对第一层为深拷贝,对其它层依然是浅拷贝。由于列表中嵌套的列表实际保存的是地址,依然指向同一个内存地址。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
if __name__ == '__main__':
a = ["a", ["b", "c", "d"], ["e", "f"]]
# 使用list.copy()方法进行拷贝,仅第一层为深拷贝
b = a.copy()
print(a)
print(b)
b[0] = "g"
b[1][0] = "f"
print(a)
print(b)
输出:
['a', ['b', 'c', 'd'], ['e', 'f']]
['a', ['b', 'c', 'd'], ['e', 'f']]
['a', ['f', 'c', 'd'], ['e', 'f']]
['g', ['f', 'c', 'd'], ['e', 'f']]
Process finished with exit code 0
使用copy.copy()方法进行拷贝,也仅对第一层为深拷贝,对其它层依然是浅拷贝。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import copy
if __name__ == '__main__':
a = ["a", ["b", "c", "d"], ["e", "f"]]
# 使用copy.copy()方法进行拷贝,仅第一层为深拷贝
b = copy.copy(a)
print(a)
print(b)
b[0] = "g"
b[1][0] = "f"
print(a)
print(b)
输出:
['a', ['b', 'c', 'd'], ['e', 'f']]
['a', ['b', 'c', 'd'], ['e', 'f']]
['a', ['f', 'c', 'd'], ['e', 'f']]
['g', ['f', 'c', 'd'], ['e', 'f']]
Process finished with exit code 0
使用copy.deepcopy()方法进行拷贝,对所有层均为深拷贝,改变新列表并不会影响到原列表,推荐使用。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import copy
if __name__ == '__main__':
a = ["a", ["b", "c", "d"], ["e", "f"]]
# 使用copy.deepcopy()方法进行拷贝,对所有层均为深拷贝
b = copy.deepcopy(a)
print(a)
print(b)
b[0] = "g"
b[1][0] = "f"
print(a)
print(b)
输出:
['a', ['b', 'c', 'd'], ['e', 'f']]
['a', ['b', 'c', 'd'], ['e', 'f']]
['a', ['b', 'c', 'd'], ['e', 'f']]
['g', ['f', 'c', 'd'], ['e', 'f']]
Process finished with exit code 0
数据展示与简单处理:当需要创建一个列表的副本用于展示数据或进行一些简单的、不会影响嵌套对象的处理时,浅拷贝足够满足需求。例如,在生成一个报表数据时,对原始数据列表进行浅拷贝,然后对副本进行格式调整或筛选操作,由于不会修改嵌套对象,浅拷贝可以快速完成任务且节省资源。
函数参数传递:在函数调用中,如果函数只是读取列表中的元素而不会修改嵌套对象,使用浅拷贝传递参数可以避免在函数内部对原始列表的意外修改。例如,一个函数用于计算列表中所有元素的和,浅拷贝参数列表可以保证函数内部的操作不会影响到原始列表。
数据独立性要求高的场景:在需要对列表进行深度修改且要求修改后的结果与原始列表完全独立的情况下,深拷贝是必需的。例如,在进行数据挖掘或机器学习中的数据预处理时,可能需要对原始数据进行多种不同的转换和分析,每种分析都需要在独立的数据副本上进行,此时深拷贝可以确保不同分析之间的数据互不干扰。
复杂数据结构的复制:对于具有复杂嵌套结构的列表,如包含多层嵌套的列表、字典等数据结构,且需要完整保留原始数据结构并进行独立操作时,深拷贝能够保证数据的完整性和独立性。例如,在处理一个表示复杂网络结构的嵌套列表时,深拷贝可以创建一个独立的网络副本,以便进行网络结构的修改和优化研究。
在 Python 中,列表的浅拷贝和深拷贝在数据复制的深度、内存使用和数据独立性方面存在显著差异。浅拷贝以较低的资源消耗快速创建列表的副本,但对于嵌套对象仅复制引用;深拷贝则确保了数据的完全独立性,但可能会消耗大量资源。
在实际编程中,应根据具体的需求和场景来选择合适的拷贝方式。如果不确定是否需要深拷贝,建议先考虑浅拷贝,并在测试过程中仔细检查对嵌套对象的修改是否会影响原始列表。如果发现浅拷贝导致的数据相互影响问题,再根据实际情况评估是否采用深拷贝。同时,在处理大型数据结构时,需要充分考虑深拷贝可能带来的性能和内存问题,可以通过优化数据结构或采用其他数据处理策略来减轻这些影响,以实现高效、准确且可靠的 Python 编程实践。