我如何预先准备一个简短的 Python 列表?

list.append()追加到列表的末尾。由于对大型列表的性能考虑,list.prepend()不存在。对于短列表,如何预置值?

385208 次浏览

s.insert(0, x)格式是最常见的。

无论何时你看到它,可能是时候考虑使用collections.deque而不是列表了。前置到双端队列以常数时间运行。前置到列表以线性时间运行。

这将创建一个带有x前缀的新列表,而不是修改现有列表:

new_list = [x] + old_list

如果有人像我一样发现这个问题,下面是我对所提出方法的性能测试:

Python 2.7.8


In [1]: %timeit ([1]*1000000).insert(0, 0)
100 loops, best of 3: 4.62 ms per loop


In [2]: %timeit ([1]*1000000)[0:0] = [0]
100 loops, best of 3: 4.55 ms per loop


In [3]: %timeit [0] + [1]*1000000
100 loops, best of 3: 8.04 ms per loop

如您所见,insert和切片赋值几乎是显式添加的两倍,并且结果非常接近。正如Raymond Hettinger所指出的,insert是更常见的选项,我个人更喜欢这种方式,而不是在列表之前。

短python列表前缀的惯用语法是什么?

您通常不想在Python中重复地添加列表。

如果列表是<强>短,并且你没有做很多……那么可以。

list.insert

list.insert可以这样使用。

list.insert(0, x)

但这是低效的,因为在Python中,list是一个指针数组,Python现在必须获取列表中的每个指针并将其向下移动1以将指针插入到第一个槽中的对象,所以这实际上只对相当短的列表有效,正如你所问。

这是一个实现它的片段从CPython源代码-正如您所看到的,我们从数组的末尾开始,每次插入都将所有内容向下移动一个:

for (i = n; --i >= where; )
items[i+1] = items[i];

如果你想要一个能够有效地前置元素的容器/列表,你需要一个链表。Python有一个双向链表,可以快速插入开头和结尾-它被称为deque

deque.appendleft

collections.deque有许多列表的方法。list.sort是个例外,使得deque绝对不能完全替代list

>>> set(dir(list)) - set(dir(deque))
{'sort'}

deque还有一个appendleft方法(以及popleft)。deque是一个双端队列和一个双向链表-无论长度如何,它总是花费相同的时间来准备一些东西。在大O符号中,列表的O(1)与O(n)时间。用法如下:

>>> import collections
>>> d = collections.deque('1234')
>>> d
deque(['1', '2', '3', '4'])
>>> d.appendleft('0')
>>> d
deque(['0', '1', '2', '3', '4'])

deque.extendleft

同样相关的是deque的extendleft方法,它迭代地前置:

>>> from collections import deque
>>> d2 = deque('def')
>>> d2.extendleft('cba')
>>> d2
deque(['a', 'b', 'c', 'd', 'e', 'f'])

请注意,每个元素将一次添加一个,从而有效地颠倒它们的顺序。

listdeque的性能对比

首先,我们设置一些迭代前置:

import timeit
from collections import deque




def list_insert_0(prepends: int):
l = []
for i in range(prepends):
l.insert(0, i)


def list_slice_insert(prepends):
l = []
for i in range(prepends):
l[:0] = [i]      # semantically same as list.insert(0, i)


def list_add(prepends):
l = []
for i in range(prepends):
l = [i] + l      # caveat: new list each time


def deque_appendleft(prepends):
d = deque()
for i in range(prepends):
d.appendleft(i)  # semantically same as list.insert(0, i)


def deque_extendleft(prepends):
d = deque()
d.extendleft(range(prepends)) # semantically same as deque_appendleft above

还有一个用于分析的函数,以便我们可以公平地比较一系列用法中的所有操作:

def compare_prepends(n, runs_per_trial):
results = {}
for function in (
list_insert_0, list_slice_insert,
list_add, deque_appendleft, deque_extendleft,
):
shortest_time = min(timeit.repeat(
lambda: function(n), number=runs_per_trial))
results[function.__name__] = shortest_time
ranked_methods = sorted(results.items(), key=lambda kv: kv[1])
for name, duration in ranked_methods:
print(f'{name} took {duration} seconds')

和性能(调整每个试验的运行次数以补偿更多前置项的较长运行时间-repeat默认进行三次试验):

compare_prepends(20, 1_000_000)
compare_prepends(100, 100_000)
compare_prepends(500, 100_000)
compare_prepends(2500, 10_000)
>>> compare_prepends(20, 1_000_000)
deque_extendleft took 0.6490256823599339 seconds
deque_appendleft took 1.4702797569334507 seconds
list_insert_0 took 1.9417422469705343 seconds
list_add took 2.7092894352972507 seconds
list_slice_insert took 3.1809083241969347 seconds
>>> compare_prepends(100, 100_000)
deque_extendleft took 0.1177942156791687 seconds
deque_appendleft took 0.5385235995054245 seconds
list_insert_0 took 0.9471780974417925 seconds
list_slice_insert took 1.4850486349314451 seconds
list_add took 2.1660344172269106 seconds
>>> compare_prepends(500, 100_000)
deque_extendleft took 0.7309095915406942 seconds
deque_appendleft took 2.895373275503516 seconds
list_slice_insert took 8.782583676278591 seconds
list_insert_0 took 8.931685039773583 seconds
list_add took 30.113558700308204 seconds
>>> compare_prepends(2500, 10_000)
deque_extendleft took 0.4839253816753626 seconds
deque_appendleft took 1.5615574326366186 seconds
list_slice_insert took 6.712615916505456 seconds
list_insert_0 took 13.894083382561803 seconds
list_add took 72.1727528590709 seconds


双端队列要快得多。随着列表变长,双端队列的性能会更好。如果您可以使用双端队列的extendleft,您可能会以这种方式获得最佳性能。

如果必须使用列表,请记住,对于小列表,list.insert工作更快,但对于较大的列表,使用切片表示法插入会更快。

不要预先列出清单

列表是用来附加的,而不是前置的。如果你有这样的情况,这种前置会损害你的代码的性能,要么切换到双端队列,或者,如果你可以颠倒你的语义学并实现同样的目标,颠倒你的列表并附加。

一般来说,避免在内置的Pythonlist对象之前添加前缀。

在我看来,在Python中,将元素或列表前置到另一个列表中的最优雅和惯用的方法是使用扩展运算符*(也称为解包运算符),

# Initial list
l = [4, 5, 6]


# Modification
l = [1, 2, 3, *l]

修改后的结果列表为[1, 2, 3, 4, 5, 6]

我也喜欢简单地用运算符+组合两个列表,如图所示,

# Prepends [1, 2, 3] to l
l = [1, 2, 3] + l


# Prepends element 42 to l
l = [42] + l

我不喜欢另一种常见的方法,l.insert(0, value),因为它需要一个神奇的数字。此外,insert()只允许在单个元素之前添加前缀,但是上面的方法对于前缀单个元素或多个元素具有相同的语法。

让我们来看看4种方法

  1. 使用插入()
>>>
>>> l = list(range(5))
>>> l
[0, 1, 2, 3, 4]
>>> l.insert(0, 5)
>>> l
[5, 0, 1, 2, 3, 4]
>>>
  1. 使用[]和+
>>>
>>> l = list(range(5))
>>> l
[0, 1, 2, 3, 4]
>>> l = [5] + l
>>> l
[5, 0, 1, 2, 3, 4]
>>>
  1. 使用切片
>>>
>>> l = list(range(5))
>>> l
[0, 1, 2, 3, 4]
>>> l[:0] = [5]
>>> l
[5, 0, 1, 2, 3, 4]
>>>
  1. 使用collections.deque.appendleft()
>>>
>>> from collections import deque
>>>
>>> l = list(range(5))
>>> l
[0, 1, 2, 3, 4]
>>> l = deque(l)
>>> l.appendleft(5)
>>> l = list(l)
>>> l
[5, 0, 1, 2, 3, 4]
>>>

我会在python>=3.0中做一些非常快速的事情

list=[0,*list]

这可能不是最有效的方式,但在我看来,这是最Pythonic的方式。