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

2025-01-15 08:45:00
admin
原创
98
摘要:问题描述:我已经看到实际上有两种(可能更多)方法可以在 Python 中连接列表:一种方法是使用该extend()方法:a = [1, 2] b = [2, 3] b.extend(a) 另一种方法是使用加号 (+) 运算符:b += a 现在我想知道:这两个选项中的哪一个是进行列表连接的“pythonic”...

问题描述:

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

一种方法是使用该extend()方法:

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

另一种方法是使用加号 (+) 运算符:

b += a

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


解决方案 1:

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

这其实没什么可担心的,除非你要执行这个操作数十亿次。不过,瓶颈很可能在其他地方。

解决方案 2:

不能对非局部变量(不是函数局部的也不是全局的变量)使用 +=

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*

解决方案 3:

+=仅当语句也可以使用=符号时才有效,即左侧可以赋值。这是因为a += b实际上变成了a = a.__iadd__(b)内部函数。因此,如果a是无法赋值的东西(无论是通过语法还是语义),例如函数调用或不可变容器的元素,版本+=将失败。

示例 1:函数调用

您不能分配给函数调用(Python 的语法禁止这样做),所以您也不能+=直接分配函数调用的结果:

list1 = [5, 6]
list2 = [7, 8]

def get_list():
    return list1

get_list().extend(list2)  # works
get_list() += list2  # SyntaxError: can't assign to function call

示例 2:不可变容器的元素

也许更奇怪的情况是当列表是不可变容器的元素时,例如元组:

my_tuple = ([1, 2], [3, 4], [5, 6])
my_tuple[0].extend([10, 11])  # works
my_tuple[0] += [10, 11]  # TypeError: 'tuple' object does not support item assignment

既然你做不到my_tuple[0] = something,那你也做不到+=

综上所述,能用就能+==

解决方案 4:

ADD其实, 、INPLACE_ADD和三个选项还是有区别的,extend前者总是比较慢,而后两个则差不多。

有了这些信息,我宁愿使用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

解决方案 5:

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

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

它将返回错误

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

b.extend(a)完美运行

解决方案 6:

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 做了同样的事情。

解决方案 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 的评论很好,很清楚。

解决方案 8:

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

解决方案 9:

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

这些信息恰好埋藏在编程常见问题解答中:

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

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

解决方案 10:

运算+=符的速度几乎可以忽略不计,甚至比list.extend()dalonsoa 的答案所证实的要快。您实际上是在用一个方法调用来交换另外两个操作。

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

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

+=很可能由于操作的原因,不适用于元组中的列表( STORE_SUBSCRJann Poppinga 的答案)。但请注意,在这种情况下list.__iadd__()(作为方法调用) 运行良好。

不会+=创建新列表(ding 的回答)。


我很抱歉将这一切作为答案发布,我没有足够的声誉来发表评论。

解决方案 11:

当列表为元组时,仅可使用 .extend()

这将有效

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

虽然这不会

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

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

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

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

解决方案 12:

根据Python进行数据分析。

“请注意,通过加法连接列表是一种相对昂贵的操作,因为必须创建新列表并复制对象。使用扩展将元素附加到现有列表通常是更好的选择,尤其是在构建大型列表时。”因此,

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

比连接替代方法更快:

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

在此处输入图片描述
在此处输入图片描述

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   1565  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1354  
  信创国产芯片作为信息技术创新的核心领域,对于推动国家自主可控生态建设具有至关重要的意义。在全球科技竞争日益激烈的背景下,实现信息技术的自主可控,摆脱对国外技术的依赖,已成为保障国家信息安全和产业可持续发展的关键。国产芯片作为信创产业的基石,其发展水平直接影响着整个信创生态的构建与完善。通过不断提升国产芯片的技术实力、产...
国产信创系统   21  
  信创生态建设旨在实现信息技术领域的自主创新和安全可控,涵盖了从硬件到软件的全产业链。随着数字化转型的加速,信创生态建设的重要性日益凸显,它不仅关乎国家的信息安全,更是推动产业升级和经济高质量发展的关键力量。然而,在推进信创生态建设的过程中,面临着诸多复杂且严峻的挑战,需要深入剖析并寻找切实可行的解决方案。技术创新难题技...
信创操作系统   27  
  信创产业作为国家信息技术创新发展的重要领域,对于保障国家信息安全、推动产业升级具有关键意义。而国产芯片作为信创产业的核心基石,其研发进展备受关注。在信创国产芯片的研发征程中,面临着诸多复杂且艰巨的难点,这些难点犹如一道道关卡,阻碍着国产芯片的快速发展。然而,科研人员和相关企业并未退缩,积极探索并提出了一系列切实可行的解...
国产化替代产品目录   28  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用