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


542

list.append()是添加到列表末尾的明显选择。这是有关失踪人员的合理解释list.prepend()。假设我的清单很短并且对性能的关注可以忽略不计,

list.insert(0, x)

要么

list[0:0] = [x]

惯用的?

Answers:


782

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

但是,无论何时看到它,都可能是时候考虑使用collections.deque而不是列表了。


9
“但是,只要您看到它,也许就该考虑使用collections.deque而不是列表了。” 为什么是这样?
Matt M.

6
@MattM。如果您在列表的前面插入,则python必须将所有其他项向前移动一个空格,列表不能“在前面增加空间”。collections.deque(双头队列)支持“在前面放置空间”,并且在这种情况下要快得多。
fejfo

265

如果可以使用功能性方法,则以下内容很清楚

new_list = [x] + your_list

当然,你还没有插入xyour_list,而你已经创建了一个新的列表xpreprended它。


45
如您所见,这并不在列表之前。它正在创建一个新列表。因此,它根本不能满足这个问题。
克里斯·摩根

111
虽然它不能满足问题,但仍将其四舍五入,这就是本网站的目的。感谢您的评论,您是对的,但是当人们搜索该评论时,看到它会很有帮助。
dave4jr

2
同样,如果您想将列表放在列表的前面,则无法按预期使用插入。但是这种方法呢!
加塔

89

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

通常,您不想在Python中重复地放在列表之前。

如果它很,并且您没有做很多...那么就可以了。

list.insert

list.insert可以采用这种方式。

list.insert(0, x)

但这是无效的,因为在Python中,a list是一个指针数组,并且Python现在必须获取列表中的每个指针并将其向下移动一个,以将指向对象的指针插入第一个插槽中,因此,这实际上仅是有效的根据您的要求列出较短的清单。

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

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

如果您希望容器/列表能够高效地添加元素,则需要一个链表。Python有一个双向链表,可以在开头和结尾快速插入-称为a deque

deque.appendleft

A collections.deque具有列表的许多方法。list.sort是一个例外,deque绝对不能完全用Liskov代替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

与此相关的还有双端队列的extendleft方法,该方法反复进行:

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

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

listvs的表现deque

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

import timeit
from collections import deque

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

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

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

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

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

和性能:

>>> min(timeit.repeat(list_insert_0))
2.8267281929729506
>>> min(timeit.repeat(list_slice_insert))
2.5210217320127413
>>> min(timeit.repeat(list_add))
2.0641671380144544
>>> min(timeit.repeat(deque_appendleft))
1.5863927800091915
>>> min(timeit.repeat(deque_extendleft))
0.5352169770048931

双端队列更快。随着列表的增加,我希望双端队列的性能更好。如果您可以使用双端队列,则extendleft可能会以这种方式获得最佳性能。


57

如果有人像我一样发现这个问题,这是我对建议方法的性能测试:

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是更常见的选择,我个人更喜欢这种方式优先列出。


11
该测试缺少的一件事是复杂性。尽管前两个选项具有恒定的复杂度(列表中有更多元素时不会变慢),而第三个选项具有线性复杂度(取决于列表中元素的数量会变慢),因为它总是必须复制整个列表。如果列表中有更多元素,结果可能会变得更糟。
达卡龙

6
@Dakkaron我认为你错了。不少资料都列举了list.insert的线性复杂度,例如,这个漂亮的表格,并由提问者链接到的合理解释中暗示。我怀疑CPython在前两种情况下会在列表中的内存中重新分配每个元素,因此这三种都可能具有线性复杂度。我实际上并没有查看代码或自己进行测试,如果这些来源有误,请您谅解。Collections.deque.appendleft确实具有您正在谈论的线性复杂度。
TC Proctor

@Dakkaron不是真的,所有这些都具有同等的复杂性。尽管.insert就地[0:0] = [0]工作,它们仍然必须重新分配整个缓冲区。
juanpa.arrivillaga

这些基准是不好的。初始列表应在单独的设置步骤中创建,而不是计时本身的一部分。最后一个创建一个新列表,长度为1000001,因此与其他两个变异的就地版本相比,苹果和橙子更是如此。
维姆
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.