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


243

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

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

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

b += a

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


1
也许差异有更多的含义,当谈到鸭子类型,如果你也许-不确实,一个列表,但样一个列表支持.__iadd__()/ .__add__()/ .__radd__().extend()
尼克牛逼

Answers:


214

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

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


16
也许差异有更多的含义,当谈到鸭子类型,如果你也许-不确实,一个列表,但样一个列表支持.__iadd__()/ .__add__()/ .__radd__().extend()
尼克牛逼

8
这个答案没有提到重要的范围差异。
wim

3
好吧,实际上,扩展比INPLACE_ADD()(即列表串联)要快。gist.github.com/mekarpeles/3408081
Archit Kapoor,

178

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

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*


4
我对您的解释“ 在函数中不是局部变量,也不全局变量”的解释感到困难,您能举一个这样的变量的例子吗?
Stephane Rolland 2014年

8
在我的示例中,变量“ l”就是这种类型。它不是'foo'和'boo'函数的本地变量(不在它们的范围之内),但不是全局的(在'main'func内部定义,不在模块级别)
monitorius

3
我可以确认python 3.4.2仍然会发生此错误(您需要添加括号才能打印,但其他所有内容都可以保持不变)。
trichoplax

7
那就对了。但是至少您可以在Python3 中的boo中使用非本地l语句。
monitorius

编译器->解释器?
joelb

42

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

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

8

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

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

它将返回错误

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

b.extend(a) 完美运作


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

4

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()方法与迭代无关。
wombatonfire

.extend是列表类的方法。从Python文档中:list.extend(iterable) Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable.猜猜我回答了我自己的星号。
grofte

哦,您的意思是您可以传递任何可扩展的extend()。我将其读取为“ extend()可用于任何可迭代” :)我很糟糕,但这听起来有点模棱两可。
wombatonfire

1
总而言之,这不是一个好例子,至少在这个问题的背景下不是这样。当您将+=运算符与不同类型的对象一起使用时(与问题中的两个列表相反),您不能指望会得到对象的串联。您不能期望会list返回一个类型。看一下您的代码,您得到了numpy.ndarray而不是list
wombatonfire

2

其实,有三个选项之间的差异: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

2
您无法ADDINPLACE_ADD和进行比较extend()ADD生成一个新列表,并将两个原始列表的元素复制到该列表。可以肯定这将是比就地操作慢INPLACE_ADDextend()
wombatonfire

我知道。这个例子的重点是比较将所有元素放在一起的列表的不同方式。当然,由于它执行不同的操作会花费更长的时间,但是如果您有兴趣保留原始对象,那么仍然很高兴知道。
dalonsoa


-1

根据Python进行数据分析。

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

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

比串联的替代方法更快:

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

在此处输入图片说明 在此处输入图片说明


4
everything = everything + temp不一定以与相同的方式实现everything += temp
David Harrison

1
你是对的。感谢您的提醒。但是我的意思是关于效率的差异。:)
littlebear333 '18

6
@ littlebear333 everything += temp的实现方式everything无需复制。这几乎可以使您的回答变得毫无根据。
nog642
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.