为什么list.remove比列表推导快?

3

我不喜欢以下从列表中移除元素的方式:

try:
    lst.remove(elt)
except ValueError:
    pass

我知道在Python中使用 try except 块是可以的,而且我实际上也使用它们,但在这种特定情况下,我希望有一个 list.remove_if_exists(elt) 方法,当元素不在列表中时,我不需要处理该情况。

为了让事情更清晰,我尝试使用列表推导:

lst = [x for x in lst if x != elt]

然而,这种方法的速度较慢:
In [3]: %timeit [x for x in lst if x != elt]
1000 loops, best of 3: 334 µs per loop

In [4]: %timeit lst[:].remove(elt)
10000 loops, best of 3: 42.8 µs per loop

为什么会这样?如何以优雅高效的方式从列表中删除一个元素,无论它是否存在?
编辑:有人提到原因是list.remove在找到元素后停止,而列表推导式遍历所有元素,因此速度应该较慢。
因此,我尝试删除列表中的最后一个元素elt = lst[-1],以便两个过程都达到列表的末尾:
In [7]: %timeit [x for x in lst if x != elt]
1000 loops, best of 3: 343 µs per loop

In [8]: %timeit lst[:].remove(elt)
10000 loops, best of 3: 143 µs per loop

为什么list.remove的速度仍然比列表推导式快?大约快两倍。

附注:我仍然希望得到有关优雅和高效地从列表中删除元素而不关心其实际成员资格的建议。


2
remove 只会移除第一个匹配的元素。而你的 comprehension 会移除所有匹配的元素。(这并不一定解释了性能问题,只是有一点点的区别。) - Alex Riley
@kofemann 好吧,我是想知道为什么一个比另一个更快;至于备选方案,是的,我目前正在使用那个,但我认为一行代码会更简洁。 - dabadaba
@ajcr 没错。对于我的情况,我只关心一个元素。 - dabadaba
1
值得注意的是,您的列表推导式等同于内置的 filter(function, iterable) :) - Take_Care_
1
这真的取决于元素的位置,尝试创建一个 lst = [1, 2, 3, 4, 5] 并移除 5remove 仍然稍微快一些,但最慢和最快运行之间的差异非常有趣。 - zipa
显示剩余4条评论
1个回答

4

如评论中所提到的,无论列表内容是什么,您的列表理解都是O(n),而remove将遍历列表,直到第一个元素存在并且然后会退出。因此,这取决于要删除的元素的位置。

remove之所以快得多的第二个原因是它是用C实现的,解释器调用魔术方法__eq__的开销,而C代码调用C函数PyObject_RichCompareBool

您可以在此处查看源代码:

https://svn.python.org/projects/python/trunk/Objects/listobject.c

搜索listremove


我明白list.remove之所以明显更快,是因为它在找到元素时就停止了,而列表推导式则需要遍历整个列表。然而,如果您查看我的编辑,即使list.remove也到达列表末尾,它仍然比列表推导式更快。我认为原因恰恰是您指出的第二个原因?或者可能还有其他原因吗? - dabadaba
确实是第二个原因,@dabadaba。 - trincot
1
没错,你不能将Python的解释器与纯C代码进行比较。C代码没有像解释器那样的开销(对象、方法查找、列表构建等)。 - Or Duan
列表推导式必须创建一个新的列表。list.remove 只是修改现有的列表。这样做的工作量要少得多。 - Barmar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接