通过使其等于切片或使用 del 来截断列表更快吗?

Aug*_*sta 6 python optimization list slice micro-optimization

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

如果我删除第一个 n元素,答案会改变吗TruncList,就像TruncList = TruncList[n:]vs一样del TruncList[:n]

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

我想重新定义方法可能会更慢,因为它会迭代TruncList然后重新分配它,同时del截断列表,但我不确定是否是这种情况。

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

Mar*_*ers 7

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

在 CPython 中,该list类型使用动态过度分配策略来避免过于频繁地调整底层 C 数组的大小。有一个array用来容纳元件的支架,并且它始终保持稍大一些。

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

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

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

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


Aug*_*sta 5

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:]
Run Code Online (Sandbox Code Playgroud)

...并得到这些结果:

>>> 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
Run Code Online (Sandbox Code Playgroud)

看起来速度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
Run Code Online (Sandbox Code Playgroud)

del还是比较快的。

这是一个几乎所有列表元素都被删除的测试:trunc = 80并且TruncList = 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
Run Code Online (Sandbox Code Playgroud)

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

  • 您可以在设置参数中使用“from __main__ import ...”(“timeit”的第二个参数从交互式解释器导入名称,然后使用字符串作为第一个参数来使用这些名称。“from __main__ import slice_front as”例如,test, long_list_of_lists; testdata = iter(long_list_of_lists)`,第一个参数可以是 `test(next(testdata))`,以在 `timeit` 运​​行的每次迭代中将 `long_list_of_lists` 的每个元素传递给 `slice_front` 。 (2认同)