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

问题:连接两个列表-‘+ =’和extend()之间的区别

我已经看到在Python中实际上有两种(也许更多)串联列表的方法:一种方法是使用extend()方法:

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

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

b += a

现在,我想知道:这两个选项中的哪一个是列表连接的“ pythonic”方式,并且两者之间有区别(我查看了官方的Python教程,但找不到有关此主题的任何信息)。

I’ve seen there are actually two (maybe more) ways to concatenate lists in Python: One way is to use the extend() method:

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

the other to use the plus(+) operator:

b += a

Now I wonder: Which of those two options is the ‘pythonic’ way to do list concatenation and is there a difference between the two (I’ve looked up the official Python tutorial but couldn’t find anything anything about this topic).


回答 0

字节码级别的唯一区别在于,该.extend方式涉及函数调用,在Python中,该函数的调用成本比INPLACE_ADD

除非您要执行数十亿次此操作,否则实际上不必担心。但是,瓶颈可能在其他地方。

The only difference on a bytecode level is that the .extend way involves a function call, which is slightly more expensive in Python than the INPLACE_ADD.

It’s really nothing you should be worrying about, unless you’re performing this operation billions of times. It is likely, however, that the bottleneck would lie some place else.


回答 1

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

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

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

    def boo():
        l += [5]

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

main()

这是因为对于扩展情况,编译器将l使用LOAD_DEREF指令加载变量,而对于+ =,它将使用LOAD_FAST-,您将获得*UnboundLocalError: local variable 'l' referenced before assignment*

You can’t use += for non-local variable (variable which is not local for function and also not global)

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

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

    def boo():
        l += [5]

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

main()

It’s because for extend case compiler will load the variable l using LOAD_DEREF instruction, but for += it will use LOAD_FAST – and you get *UnboundLocalError: local variable 'l' referenced before assignment*


回答 2

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

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

You can chain function calls, but you can’t += a function call directly:

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

回答 3

我会说numpy附带一些区别(我刚刚看到问题是关于连接两个列表而不是numpy数组,但是由于这对像我这样的初学者来说可能是个问题,我希望这可以对某人有所帮助寻求解决此职位的人),例如

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

它将返回错误

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

b.extend(a) 完美运作

I would say that there is some difference when it comes with numpy (I just saw that the question ask about concatenating two lists, not numpy array, but since it might be a issue for beginner, such as me, I hope this can help someone who seek the solution to this post), for ex.

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

it will return with error

ValueError: operands could not be broadcast together with shapes (0,) (4,4,4)

b.extend(a) works perfectly


回答 4

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;
}

From the CPython 3.5.2 source code: No big difference.

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;
}

回答 5

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() works with any iterable*, += works with some but can get funky.

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
*pretty sure .extend() works with any iterable but please comment if I am incorrect


回答 6

其实,有三个选项之间的差异:ADDINPLACE_ADDextend。前者总是较慢,而另两个大致相同。

有了这些信息,我宁愿使用extend,它比更快ADD,并且在我看来比更加明确INPLACE_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

Actually, there are differences among the three options: ADD, INPLACE_ADD and extend. The former is always slower, while the other two are roughly the same.

With this information, I would rather use extend, which is faster than ADD, and seems to me more explicit of what you are doing than INPLACE_ADD.

Try the following code a few times (for 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

回答 7

我已经查阅了官方的Python教程,但是找不到关于此主题的任何内容

这些信息恰好埋在“ 编程常见问题”中

…对于列表,__iadd__[ie +=]等效于调用extend列表并返回列表。这就是为什么我们说列表+=是“简写”的原因list.extend

您还可以在CPython源代码中亲自查看以下内容:https : //github.com/python/cpython/blob/v3.8.2/Objects/listobject.c#L1000-L1011

I’ve looked up the official Python tutorial but couldn’t find anything anything about this topic

This information happens to be buried in the Programming FAQ:

… for lists, __iadd__ [i.e. +=] is equivalent to calling extend on the list and returning the list. That’s why we say that for lists, += is a “shorthand” for list.extend

You can also see this for yourself in the CPython source code: https://github.com/python/cpython/blob/v3.8.2/Objects/listobject.c#L1000-L1011


回答 8

根据Python进行数据分析。

“请注意,通过添加进行列表连接是一项相对昂贵的操作,因为必须创建新列表并复制对象。通常最好使用extend将元素追加到现有列表中,特别是在构建大型列表时。”因此,

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

比串联的替代方法更快:

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

According to the Python for Data Analysis.

“Note that list concatenation by addition is a comparatively expensive operation since a new list must be created and the objects copied over. Using extend to append elements to an existing list, especially if you are building up a large list, is usually preferable. ” Thus,

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

is faster than the concatenative alternative:

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