Python遍历时删除元素问题(附深拷贝与浅拷贝介绍)

发布时间 2023-04-02 11:09:31作者: XuShuo_Self

问题

有时候,我们希望用Python遍历一个列表(或其他可迭代对象),如果其中有我们不需要的元素就把它删除并继续遍历。

如以下代码段,我们本希望打印1、3,可最后却只打印了1

a = [1,2,3]

for i in a:

    if i == 2:
        a.remove(i)
    else:
        print(i)

分析

其实,之所以会有上述问题的出现,是因为元素2被删除后,3到了2的位置,导致解释器以为遍历结束,则没有继续打印3,如下图示:

[
1, <= 打印
2,
3
]

[
1,
2, <= 触发if条件,删除
3
]

[
1,
3 <= 删除“2”后“3”到了“2”的位置,
    解释器误以为没有下一个可迭代元素,
    结束遍历
]

解决

为了解决上述问题,我们可以建立一个原数据的副本,使正本数据正常遍历,而删减副本中的数据,如此我们便可以同时得到遍历结果和删减后的列表了,如下代码:

import copy

a = [1,2,3]
b = copy.deepcopy(a) # 或 b = a.copy()

for i in a:
    if i == 2:
        b.remove(i)
    else:
        print(i)

print('原始数据',a,'删减后元素:',b)

输出:

1
3
原始数据 [1, 2, 3] 删减后元素: [1, 3]

扩展

您可能会注意到,以下解法是错误的,其还是只会打印1

a = [1,2,3]
b = a

for i in a:
    if i == 2:
        b.remove(i)
    else:
        print(i)

其实,上述的“赋值”操作其实就是传递了对象的引用(即在内存中的位置),a和b实际指向的是同一个列表。

我们可以用id函数来证实这一点:

import copy
va = [1,2,3]
vb = va
vc = copy.deepcopy(va)
vd = va.copy()

print(id(va), id(vb), id(vc), id(vd))

结果:2336177198528 2336177198528 2336177114176 2336177226432

我们可以看到,va和vb的id是相同的,而它们与vc、vd的id却是不同的。这是因为深拷贝后程序会新开辟一块内存空间,并把原来的数据复制过去。此时,改变vc、vd的值就和va没有关系了。

  • 会使用浅拷贝的数据类型:list类型(列表类型)、set类型(数组类型)、dict类型(列表类型)
  • 不会使用浅拷贝的数据类型:str类型(字符串类型)、int类型(整数类型)、float类型(浮点数类型)、complex类型(复数类型)、tuple类型(元组类型)

除了用copy库的deepcopy方法以外,会使用浅拷贝的数据类型一般还内置了copy方法,如下:

# 列表
la = [1,2,3]
lb = la.copy()

# 集合
sa = {1,2,3}
sb = sa.copy()

# 字典
da = {1:1,2:2,3:3}
db = da.copy()

其也可以起到和copy.deepcopy相同的作用。