686

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

list.insert(0, x)

或者

list[0:0] = [x]

惯用语?

4

6 回答 6

965

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

但是,无论何时看到它,可能是时候考虑使用collections.deque而不是列表了。在双端队列之前运行在恒定时间内。预先添加到列表以线性时间运行。

于 2011-12-16T18:08:26.647 回答
342

如果你可以走功能性的方式,以下是很清楚的

new_list = [x] + your_list

当然,您还没有插入xyour_list而是创建了一个新列表,并x预先添加了它。

于 2012-06-05T06:25:16.383 回答
118

附加到简短的 python 列表的惯用语法是什么?

您通常不希望在 Python 中重复地添加到列表中。

如果列表很,并且您没有做很多......那么好吧。

list.insert

list.insert可以这样使用。

list.insert(0, x)

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

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

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

如果您想要一个在前置元素方面有效的容器/列表,您需要一个链表。Python 有一个双向链表,可以快速插入到开头和结尾——它叫做deque.

deque.appendleft

Acollections.deque有许多列表的方法。list.sort是一个例外,这使得dequeLiskov 绝对不能完全替代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'])

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

list对比的表现deque

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

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对象之前添加。

于 2015-06-05T18:26:21.383 回答
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,这是更常见的选择,我个人更喜欢这种方式来预先列出。

于 2015-03-03T09:25:56.630 回答
7

在我看来,在 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()只允许添加单个元素,但是上面的方法具有相同的语法来添加单个元素或多个元素。

于 2020-11-07T17:23:30.013 回答
2

让我们看一下 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]
>>> 
于 2021-01-23T08:38:16.003 回答