连接两个列表 - '+=' 和 extend() 之间的区别

Concatenating two lists - difference between '+=' and extend()

提问人:helpermethod 提问时间:9/7/2010 最后编辑:vvvvvhelpermethod 更新时间:11/24/2023 访问量:109964

问:

我已经看到实际上有两种(也许更多)方法可以在 Python 中连接列表:

一种方法是使用以下方法:extend()

a = [1, 2]
b = [2, 3]
b.extend(a)

另一个使用加号 (+) 运算符:

b += a

现在我想知道:这两个选项中的哪一个是执行列表串联的“pythonic”方式,两者之间有区别吗?(我已经查找了官方的 Python 教程,但找不到有关此主题的任何内容)。

列出 python

评论

1赞 Nick T 12/16/2014
也许这种差异在鸭子打字方面有更多的含义,如果你的可能不是真的一个列表,但喜欢一个列表支持 // 与.__iadd__().__add__().__radd__().extend()

答:

297赞 SilentGhost 9/7/2010 #1

字节码级别上的唯一区别是 .extend 方式涉及函数调用,这在 Python 中比 INPLACE_ADD 稍微贵一些。

这真的没什么你应该担心的,除非你执行这个操作数十亿次。然而,瓶颈很可能在其他地方。

评论

22赞 Nick T 12/16/2014
也许这种差异在鸭子打字方面有更多的含义,如果你的可能不是真的一个列表,但喜欢一个列表支持 // 与.__iadd__().__add__().__radd__().extend()
16赞 wim 1/28/2017
这个答案没有提到重要的范围差异。
12赞 Archit 7/18/2018
实际上,extends 比 INPLACE_ADD() (即列表串联)更快。gist.github.com/mekarpeles/3408081
3赞 void.pointer 2/18/2021
对我来说,这个答案并没有真正帮助我决定我应该使用哪一个作为一般原则。我认为一致性很重要,并且知道它不能与非本地人一起使用,并且不能链接(从其他答案中)提供了比运算符更实用、更实用的理由,即使有选择。“数十亿次操作”用例是有道理的,但在我的职业生涯中,我遇到过的次数并不多。extend()
5赞 fsan 2/2/2023
.extend比 快。扩展具有额外的函数调用无关。 是一个运算符,它还会导致函数调用。之所以更快,是因为它所做的工作要少得多。 将 (1) 创建一个列表,从该列表中复制所有元素(引用),然后它将获取第二个列表并添加引用。 不会创建新列表,也不会从该列表中复制引用元素。extend 等效于 。extend 将对您正在执行操作的列表进行操作,并且应该使用而不是++.extend+.extenda[len(a):] = iterableL = L + iterable
239赞 monitorius 6/17/2014 #2

您不能将 += 用于非局部变量(对于函数来说不是局部变量,也不是全局变量)

def main():
    l = [1, 2, 3]

    def foo():
        l.extend([4])

    def boo():
        l += [5]

    foo()
    print l
    boo()  # this will fail

main()

这是因为对于扩展大小写编译器将使用指令加载变量,但对于 += 它将使用 - 你会得到lLOAD_DEREFLOAD_FAST*UnboundLocalError: local variable 'l' referenced before assignment*

评论

6赞 Stephane Rolland 8/7/2014
我对你的解释“变量不是函数的局部变量,也不是全局变量”有困难,你能举例说明这样的变量吗?
12赞 monitorius 8/7/2014
在我的例子中,变量“l”正是这种类型。对于“foo”和“boo”函数来说,它不是本地的(在它们的范围之外),但它不是全局的(在“main”函数中定义,而不是在模块级别)
3赞 trichoplax is on Codidact now 7/1/2015
我可以确认python 3.4.2仍然会出现此错误(您需要添加括号才能打印,但其他一切都可以保持不变)。
8赞 monitorius 7/2/2015
没错。但至少您可以在 Python3 的 boo 中使用非本地 l 语句。
1赞 joel 3/18/2020
编译器 ->解释器?
74赞 isarandi 6/11/2016 #3

您可以链接函数调用,但不能直接 += 函数调用:

class A:
    def __init__(self):
        self.listFoo = [1, 2]
        self.listBar = [3, 4]

    def get_list(self, which):
        if which == "Foo":
            return self.listFoo
        return self.listBar

a = A()
other_list = [5, 6]

a.get_list("Foo").extend(other_list)
a.get_list("Foo") += other_list  #SyntaxError: can't assign to function call
9赞 Lance Ruo Zhang 11/2/2016 #4

我想说的是,numpy 有一些区别(我刚刚看到这个问题是关于连接两个列表的,而不是 numpy 数组,但由于这对初学者来说可能是个问题,比如我,我希望这可以帮助那些寻求本文解决方案的人),例如。

import numpy as np
a = np.zeros((4,4,4))
b = []
b += a

它将返回错误

ValueError:操作数不能与形状 (0,) 一起广播 (4,4,4)

b.extend(a)完美工作

5赞 VicX 11/3/2016 #5

CPython 3.5.2 源代码: 没有太大的区别。

static PyObject *
list_inplace_concat(PyListObject *self, PyObject *other)
{
    PyObject *result;

    result = listextend(self, other);
    if (result == NULL)
        return result;
    Py_DECREF(result);
    Py_INCREF(self);
    return (PyObject *)self;
}
-1赞 littlebear333 7/24/2018 #6

根据 Python 进行数据分析。

“请注意,通过加法进行列表串联是一项相对昂贵的操作,因为必须创建一个新列表并复制对象。通常最好使用 extend 将元素追加到现有列表,尤其是在构建大型列表时。" 因此

everything = []
for chunk in list_of_lists:
    everything.extend(chunk)

比 Concatenative 替代方案更快:

everything = []
for chunk in list_of_lists:
    everything = everything + chunk

enter image description here enter image description here

评论

5赞 David Harrison 8/2/2018
everything = everything + temp不一定以与 相同的方式实现。everything += temp
1赞 littlebear333 8/2/2018
你是对的。谢谢你的提醒。但我的观点是关于效率的差异。: )
8赞 nog642 8/20/2018
@littlebear333的实现方式不需要复制。这几乎使你的答案成为一个有争议的问题。everything += tempeverything
6赞 grofte 2/2/2019 #7

列表上的 .extend() 方法适用于任何可迭代对象*,+= 适用于某些可迭代对象,但可能会变得时髦。

import numpy as np

l = [2, 3, 4]
t = (5, 6, 7)
l += t
l
[2, 3, 4, 5, 6, 7]

l = [2, 3, 4]
t = np.array((5, 6, 7))
l += t
l
array([ 7,  9, 11])

l = [2, 3, 4]
t = np.array((5, 6, 7))
l.extend(t)
l
[2, 3, 4, 5, 6, 7]

Python 3.6
*非常确定 .extend() 适用于任何可迭代对象,但如果我不正确,请发表评论

编辑:“extend()”更改为“列表中的.extend()方法” 注意:David M. Helmuth 在下面的评论很好,很清楚。

评论

0赞 wombatonfire 3/23/2019
Tuple 绝对是一个可迭代的,但它没有 extend() 方法。extend() 方法与迭代无关。
0赞 grofte 3/23/2019
.extend 是 List 类的方法。来自 Python 文档:猜猜我回答了我自己的星号。list.extend(iterable) Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable.
0赞 wombatonfire 3/23/2019
哦,你的意思是你可以传递任何可迭代的 extend()。我把它读作“extend() 可用于任何可迭代”:)我的错,但听起来有点模棱两可。
1赞 wombatonfire 3/23/2019
总而言之,这不是一个好例子,至少在这个问题的背景下不是。当您将运算符与不同类型的对象一起使用时(与问题中的两个列表相反),您不能期望获得对象的串联。而且你不能指望会有一个类型返回。看看你的代码,你会得到一个而不是.+=listnumpy.ndarraylist
1赞 David M. Helmuth 7/6/2022
@grofte提供了正确的答案;但是,答案需要一些澄清,所以这里是我建议的澄清:当使用 extend() 方法将列表与另一个可迭代对象中保存的值连接起来时,无论另一个可迭代对象是列表、元组还是 NumPy 数组,您都会获得一致的行为。当使用 += 运算符将第二个可迭代对象连接到列表时,情况并非如此。(见下面给出的示例) - 这就是它们的不同之处
9赞 dalonsoa 2/19/2019 #8

实际上,这三个选项之间存在差异:和 。前者总是较慢,而其他两个大致相同。ADDINPLACE_ADDextend

有了这些信息,我宁愿使用 ,它比 更快,而且在我看来,你在做什么比 更明确。extendADDINPLACE_ADD

尝试以下代码几次(对于 Python 3):

import time

def test():
    x = list(range(10000000))
    y = list(range(10000000))
    z = list(range(10000000))

    # INPLACE_ADD
    t0 = time.process_time()
    z += x
    t_inplace_add = time.process_time() - t0

    # ADD
    t0 = time.process_time()
    w = x + y
    t_add = time.process_time() - t0

    # Extend
    t0 = time.process_time()
    x.extend(y)
    t_extend = time.process_time() - t0

    print('ADD {} s'.format(t_add))
    print('INPLACE_ADD {} s'.format(t_inplace_add))
    print('extend {} s'.format(t_extend))
    print()

for i in range(10):
    test()
ADD 0.3540440000000018 s
INPLACE_ADD 0.10896000000000328 s
extend 0.08370399999999734 s

ADD 0.2024550000000005 s
INPLACE_ADD 0.0972940000000051 s
extend 0.09610200000000191 s

ADD 0.1680199999999985 s
INPLACE_ADD 0.08162199999999586 s
extend 0.0815160000000077 s

ADD 0.16708400000000267 s
INPLACE_ADD 0.0797719999999913 s
extend 0.0801490000000058 s

ADD 0.1681250000000034 s
INPLACE_ADD 0.08324399999999343 s
extend 0.08062700000000689 s

ADD 0.1707760000000036 s
INPLACE_ADD 0.08071900000000198 s
extend 0.09226200000000517 s

ADD 0.1668420000000026 s
INPLACE_ADD 0.08047300000001201 s
extend 0.0848089999999928 s

ADD 0.16659500000000094 s
INPLACE_ADD 0.08019399999999166 s
extend 0.07981599999999389 s

ADD 0.1710910000000041 s
INPLACE_ADD 0.0783479999999912 s
extend 0.07987599999999873 s

ADD 0.16435900000000458 s
INPLACE_ADD 0.08131200000001115 s
extend 0.0818660000000051 s

评论

2赞 wombatonfire 3/23/2019
您无法与 和 进行比较。 生成一个新列表,并将两个原始列表的元素复制到该列表。可以肯定的是,它会比 和 的就地操作慢。ADDINPLACE_ADDextend()ADDINPLACE_ADDextend()
4赞 dalonsoa 4/10/2019
我知道那件事。此示例的重点是比较将所有元素放在一起的列表的不同方法。当然,这需要更长的时间,因为它可以做不同的事情,但如果您有兴趣保持原始对象不变,那么知道这一点仍然很好。
3赞 Flux 3/8/2020 #9

我查找了官方的 Python 教程,但找不到有关此主题的任何内容

此信息恰好隐藏在编程常见问题解答中:

...对于列表,[即 ] 等同于调用列表并返回列表。这就是为什么我们说对于列表,是__iadd__+=extend+=list.extend

您也可以在 CPython 源代码中亲眼看到这一点:https://github.com/python/cpython/blob/v3.8.2/Objects/listobject.c#L1000-L1011

1赞 Jann Poppinga 3/9/2021 #10

当列表位于元组中时,只能使用 .extend()

这将起作用

t = ([],[])
t[0].extend([1,2])

虽然这不会

t = ([],[])
t[0] += [1,2]

原因是生成一个新对象。如果你看一下长版本:+=

t[0] = t[0] + [1,2]

您可以看到这将如何更改元组中的对象,这是不可能的。using 修改元组中的对象,这是允许的。.extend()

6赞 dniq 4/8/2022 #11

ary += ext 创建一个新的 List 对象,然后将列表 “ary” 和 “ext” 中的数据复制到其中。

ary.extend(ext) 只是将对 “ext” 列表的引用添加到 “ary” 列表的末尾,从而减少内存事务。

因此,.extend 的工作速度要快几个数量级,并且不会使用正在扩展的列表和正在扩展的列表之外的任何其他内存。

╰─➤ time ./list_plus.py
./list_plus.py  36.03s user 6.39s system 99% cpu 42.558 total
╰─➤ time ./list_extend.py
./list_extend.py  0.03s user 0.01s system 92% cpu 0.040 total

第一个脚本还使用超过 200MB 的内存,而第二个脚本使用的内存不超过“裸”python3 进程。

话虽如此,就地添加似乎确实与 .extend 做同样的事情。

评论

0赞 Niko Fohr 2/4/2023
你能在这里添加和的内容吗?/list_plus.py/list_extend.py
0赞 themeasure43 11/24/2023 #12

算子的速度可以忽略不计,如果比达隆索亚的回答所证实的要快的话。实际上,您正在将方法调用交换为另外两个操作。+=list.extend()

>>> dis.dis("_list.extend([1])")
  1           0 LOAD_NAME                0 (_list)
              2 LOAD_METHOD              1 (extend)
              4 LOAD_CONST               0 (4)
              6 BUILD_LIST               1
              8 CALL_METHOD              1
             10 RETURN_VALUE
>>> dis.dis("_list += [1]")
  1           0 LOAD_NAME                0 (_list)
              2 LOAD_CONST               0 (4)
              4 BUILD_LIST               1
              6 INPLACE_ADD
              8 STORE_NAME               0 (_list)
             10 LOAD_CONST               1 (None)
             12 RETURN_VALUE

请注意,这不适用于数组,因为数组根本不是 Python 列表,不应被视为 Python 列表(Lance Ruo Zhang 的回答)。numpynumpy

这不适用于元组中的列表,很可能是因为操作(Jann Poppinga 的答案)。但是请注意,在这种情况下(作为方法调用)工作得很好。+=STORE_SUBSCRlist.__iadd__()

不会创建新列表(丁的答案)。+=


我很抱歉将所有这些作为答案发布,我没有足够的代表来发表评论。