通过使列表等于一个切片或使用 del 来截断列表是否更快?

Is it faster to truncate a list by making it equal to a slice, or by using del?

假设我有一个 list TruncList,其中的元素数量大于 n。如果我想从该列表的末尾删除 n 元素, 将列表重新定义为自身的一部分 保留所需元素是否更快,如 TruncList = TruncList[:-n],或从列表中删除不需要的元素,如del TruncList[-n:]?

如果我从 TruncList 中删除 first n 元素,答案是否会改变,如 TruncList = TruncList[n:]del TruncList[:n]?

除了速度之外,这些方法中的一种是否比另一种更符合 Pythonic?

我认为重新定义方法可能会更慢,因为它遍历 TruncList 然后重新分配它,而 del 会在适当的位置截断列表,但我不确定是否这些都是这样。

我还认为 del 是更好的路线,因为它似乎是该功能的自然用法。

所以我自己使用 timeit 和这些样本进行了测试:

  ## Make a list of 500 elements and then remove the first 80...
def slice_front():
    "Make the list equal to all but the first eighty elements."
    trunc = 80
    TruncList = range(500)
    TruncList = TruncList[trunc:]

def del_front():
    "Use del to remove the first eighty elements."
    trunc = 80
    TruncList = range(500)
    del TruncList[:trunc]


  ## Make a list of 500 elements and then remove the last 80...
def slice_end():
    "Make the list equal to all but the last eighty elements."
    trunc = 80
    TruncList = range(500)
    TruncList = TruncList[:-trunc]

def del_end():
    "Delete the last eighty elements from the list using del."
    trunc = 80
    TruncList = range(500)
    del TruncList[-trunc:]

...并得到这些结果:

>>> timeit.timeit(slice_front, number = 66666)
1.3381525804258112
>>> timeit.timeit(del_front, number = 66666)
1.0384902281466895
>>> timeit.timeit(slice_end, number = 66666)
1.3457694381917094
>>> timeit.timeit(del_end, number = 66666)
1.026411701603827

看起来 del 更快,而且差距很大。


编辑

如果我 运行 相同的样本,但用 trunc = 2 代替,结果如下:

>>> timeit.timeit(slice_front, number = 66666)
1.3947686585537422
>>> timeit.timeit(del_front, number = 66666)
1.0224893312699308
>>> timeit.timeit(slice_end, number = 66666)
1.4089230444569498
>>> timeit.timeit(del_end, number = 66666)
1.042288032264116

del 还是更快。

这是一个几乎所有列表元素都被删除的测试:trunc = 80TruncList = range(81)...

>>> timeit.timeit(slice_front, number = 66666)
0.25171681555993247
>>> timeit.timeit(del_front, number = 66666)
0.2696609454136185
>>> timeit.timeit(slice_end, number = 66666)
0.2635454769274057
>>> timeit.timeit(del_end, number = 66666)
0.294670910710936

在这种情况下,del比重新定义方法要慢一些。

这完全取决于您删除了多少元素

在 CPython 中,list 类型使用动态过度分配策略来避免必须过于频繁地调整底层 C 数组的大小。有一个 array 来保存元素,它始终保持稍微太大。

删除然后(使用del TruncList[-n:]可能是一个几乎免费的操作,前提是n足够小。事实上,在调整大小发生之前,您最多可以安全地删除过度分配数组大小的 一半 。调整大小需要将所有现有引用复制到新数组。

使用切片 总是 创建新的列表对象,需要分配内存并复制所涉及的元素。这比重新分配数据稍微多一些工作。

因此,在不测量时间性能(使用 timeit)的情况下,我希望 del 选项比切片更快;在 n < len(TruncList) // 2 (小于长度的一半)的情况下,在许多情况下你甚至不会导致调整大小,即使你这样做了,需要做的工作也略少,因为只需要重新创建内部数组.

当您从前面删除项目时,您将始终必须重新创建内部数组。那时差异不会很明显,但是创建切片仍然会导致分配一个全新的对象。