在Python中优雅地从序列中移除项目的方法?

57 投票
14 回答
46886 浏览
提问于 2025-04-10 23:24

在我写Python代码的时候,经常需要根据一些条件从列表或者其他序列中删除某些项目。不过,我还没找到一个既优雅又高效的方法,因为在遍历一个列表的时候直接删除里面的项目是不太好的做法。比如说,你不能这样做:

for name in names:
    if name[-5:] == 'Smith':
        names.remove(name)

我通常会这样处理:

toremove = []
for name in names:
    if name[-5:] == 'Smith':
        toremove.append(name)
for name in toremove:
    names.remove(name)
del toremove

这样做效率低下,看起来也不太好,而且可能会出错(比如如果有多个“John Smith”的条目,它是怎么处理的呢?)。有没有人能提供一个更优雅的解决方案,或者至少是一个更高效的办法?

那有没有适用于字典的呢?

14 个回答

29

显而易见的答案是约翰和其他几个人给出的那个,也就是:

>>> names = [name for name in names if name[-5:] != "Smith"]       # <-- slower

不过,这种方法的缺点是它会创建一个新的列表对象,而不是重复使用原来的对象。我做了一些性能测试和实验,发现我想到的最有效的方法是:

>>> names[:] = (name for name in names if name[-5:] != "Smith")    # <-- faster

把“names[:]”赋值基本上意味着“用以下值替换names列表的内容”。这和直接给names赋值不同,因为它不会创建一个新的列表对象。赋值右边的部分是一个生成器表达式(注意使用的是圆括号而不是方括号)。这会让Python遍历整个列表。

一些快速的性能测试表明,这种方法比列表推导式快大约30%,比过滤器方法快大约40%。

注意:虽然这个解决方案比明显的解决方案快,但它比较晦涩,并且依赖于更高级的Python技巧。如果你使用它,我建议加上注释。只有在你真的关心这个特定操作的性能时,使用它才有意义(无论如何,这个操作都挺快的)。在我使用这个方法的情况下,我是在做A*搜索,并用它来从搜索束中移除搜索点。

37

你还可以反向遍历这个列表:

for name in reversed(names):
    if name[-5:] == 'Smith':
        names.remove(name)

这样做的好处是,它不会像使用 filter 或列表推导那样创建一个新的列表,而是使用一个迭代器,这样就不会复制整个列表(像 [:] 那样)。

需要注意的是,虽然在反向遍历时删除元素是安全的,但插入元素就有点复杂了。

56

有两种简单的方法可以实现过滤:

  1. 使用 filter 函数:

    names = filter(lambda name: name[-5:] != "Smith", names)

  2. 使用列表推导式:

    names = [name for name in names if name[-5:] != "Smith"]

需要注意的是,这两种方法都会保留那些经过判断后结果为 True 的值,所以你需要反过来思考逻辑(也就是说,你要说“保留那些姓氏不是 Smith 的人”,而不是“去掉那些姓氏是 Smith 的人”)。

编辑 有趣的是,在我发帖的时候,有两个人分别也发了我建议的这两个答案。

撰写回答