Python中的列表remove

问题

问题来自于Python技术群的一个提问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# -*- coding:utf-8 -*-
def filterwords(keywords):
bad_keys = [u'美丽', u'傻瓜']
# copy = keywords[::]
if keywords !=None:
# for key in copy:
for key in keywords:
for bkey in bad_keys:
if bkey in key:
keywords.remove(key)
break
return keywords
if __name__ == '__main__':
keywords=[u'哈士奇',u'二哈',u'哈士奇图片',u'哈士奇美丽价格',u'哈士奇是个大傻瓜']
res = filterwords(keywords)
for key in res:
print key

输出结果为:

1
2
3
4
哈士奇
二哈
哈士奇图片
哈士奇是个大傻瓜

代码功能很简单,就是给定两个列表keywords和badkeys,从keywords中过滤掉包含bad key的元素。乍一看代码,逻辑上没有问题。那究竟是什么原因导致执行出错了呢?

分析

首先在for循环中加了log:

1
2
3
4
for key in keywords:
for bkey in bad_keys:
print key, bkey
if bkey in key:

然后发现keywords中的最后一个元素“哈士奇是个大傻瓜”没有被处理,猜测一定是remove或者循环出了问题。
简单Google一下,果然:
For in 是对下标进行操作,而remove是对值进行操作
remove删除一个元素之后,后续元素下标会前移,前移的这个元素实际上就没处理到
具体到上面的例子:
当For in执行到“哈士奇美丽价格”时,下标为3,然后匹配了bad key,执行了remove操作,删除了该元素。此时下一个元素“哈士奇是个大傻瓜”前移,下标变成了3。For循环继续执行,发现没有下标为4的元素,于是直接退出循环了。这样,最后一个元素就没有被处理到。

解决

想到两种方法:

  1. 讲keywords数组先copy一份,然后遍历副本,操作原数组;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    copy = keywords[::]
    if keywords !=None:
    # for key in copy:
    for key in keywords:
    for bkey in bad_keys:
    if bkey in key:
    keywords.remove(key)
    break
    ...
  2. 开辟一个新数组存储合规的key,不操作原数组;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    res = []
    if keywords !=None:
    # for key in copy:
    for key in keywords:
    for bkey in bad_keys:
    if bkey in key:
    break
    else:
    res.append(key)

总之其实就是一句话:不要在遍历的同时操作循环的主体

PS

上面的方案二里出现了for…else…的语法,这里简单解释一下:
在 python 中,for … else 表示这样的意思,for 中的语句和普通的没有区别,else 中的语句会在循环正常执行完(即 for 不是通过 break 跳出而中断的)的情况下执行,while … else 也是一样。
例如:

1
2
3
4
5
6
7
8
9
10
11
#!/usr/bin/python
# -*- coding: UTF-8 -*-
for num in range(10,20): # 迭代 10 到 20 之间的数字
for i in range(2,num): # 根据因子迭代
if num%i == 0: # 确定第一个因子
j=num/i # 计算第二个因子
print '%d 等于 %d * %d' % (num,i,j)
break # 跳出当前循环
else: # 循环的 else 部分
print num, '是一个质数'

欢迎打赏!