【问题标题】:Concatenating two lists - difference between '+=' and extend()连接两个列表 - '+=' 和 extend() 之间的区别
【发布时间】:2023-03-08 20:26:02
【问题描述】:

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

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

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

b += a

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

【问题讨论】:

  • 当涉及到ducktyping并且你的maybe-not-really-a-list-but-like-a-list支持.__iadd__() /.__add__()/.__radd__().extend()

标签: list python


【解决方案1】:

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

这会起作用

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

虽然不会

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

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

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

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

【讨论】:

    【解决方案2】:

    来自CPython 3.5.2 source code: 差别不大。

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

    【讨论】:

      【解决方案3】:

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

      这个信息恰好埋在Programming FAQ

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

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

      【讨论】:

        【解决方案4】:

        其实这三个选项是有区别的: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
        

        【讨论】:

        • 您无法将ADDINPLACE_ADDextend() 进行比较。 ADD 生成一个新列表并将两个原始列表的元素复制到它。肯定会比 INPLACE_ADDextend() 的就地操作慢。
        • 我知道。这个例子的重点是比较将所有元素放在一起的列表的不同方式。当然它需要更长的时间,因为它做不同的事情,但如果你有兴趣保持原始对象不变,那么知道它仍然是一件好事。
        【解决方案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() 适用于任何可迭代但如果我不正确请评论

        【讨论】:

        • Tuple 绝对是一个可迭代的,但它没有 extend() 方法。 extend() 方法与迭代无关。
        • .extend 是列表类的一个方法。来自 Python 文档:list.extend(iterable) Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable. 猜我自己回答了星号。
        • 哦,您的意思是您可以将任何可迭代对象传递给extend()。我把它读作“extend() 可用于任何可迭代”:) 我的错,但这听起来有点模棱两可。
        • 总而言之,这不是一个很好的例子,至少在这个问题的上下文中不是。当您将 += 运算符与不同类型的对象一起使用时(与问题中的两个列表相反),您不能期望您会得到对象的串联。而且你不能指望会返回一个list 类型。看看你的代码,你会得到一个numpy.ndarray 而不是list
        【解决方案6】:

        根据 Python 进行数据分析。

        “请注意,通过添加连接列表是一项相对昂贵的操作,因为必须创建一个新列表并复制对象。使用 extend 将元素附加到现有列表中,尤其是在构建大型列表时,通常更可取。 ” 因此,

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

        比串联替代方案更快:

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

        【讨论】:

        • everything = everything + temp 的实现方式不一定与everything += temp 相同。
        • 你是对的。谢谢你的提醒。但我的观点是关于效率的差异。 :)
        • @littlebear333 everything += temp 以不需要复制everything 的方式实现。这几乎使您的回答成为一个有争议的问题。
        【解决方案7】:

        我会说 numpy 有一些不同(我刚刚看到问题是关于连接两个列表,而不是 numpy 数组,但是因为这可能是初学者的问题,比如我,我希望这个可以帮助寻求解决此帖子的人),例如。

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

        它会返回错误

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

        b.extend(a) 完美运行

        【讨论】:

          【解决方案8】:

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

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

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

            除非您执行此操作数十亿次,否则您无需担心。然而,瓶颈很可能在其他地方。

            【讨论】:

            • 当涉及到鸭式打字时,如果您的 maybe-not-really-a-list-but-like-a-list 支持 .__iadd__() /.__add__()/.__radd__().extend()
            • 这个答案没有提到重要的范围差异。
            • 实际上,extends 比 INPLACE_ADD() 更快,即列表连接。 gist.github.com/mekarpeles/3408081
            • 对我来说,这个答案并没有真正帮助我决定应该使用哪一个作为一般原则。我认为一致性很重要,并且知道它不能与非本地人一起使用,并且不能被链接(来自其他答案)提供了一个更实际、更实用的理由来使用 extend() 而不是运算符,即使有选择。 “数十亿次运营”用例是一个有效的观点,但在我的职业生涯中,我遇到的次数不多。
            【解决方案10】:

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

            def main():
                l = [1, 2, 3]
            
                def foo():
                    l.extend([4])
            
                def boo():
                    l += [5]
            
                foo()
                print l
                boo()  # this will fail
            
            main()
            

            这是因为对于 extend 案例编译器将使用 LOAD_DEREF 指令加载变量 l,但对于 += 它将使用 LOAD_FAST - 你会得到 *UnboundLocalError: local variable 'l' referenced before assignment*

            【讨论】:

            • 我对你的解释“对于函数来说不是本地而且不是全局的变量有困难”你能举一个这样的变量的例子吗?
            • 我的例子中的变量 'l' 就是这种类型。对于 'foo' 和 'boo' 函数(在它们的范围之外),它不是本地的,但它不是全局的(在 'main' func 内部定义,而不是在模块级别)
            • 我可以确认 python 3.4.2 仍然会出现此错误(您需要添加括号才能打印,但其他所有内容都可以保持不变)。
            • 没错。但至少你可以在 Python3 的 boo 中使用 nonlocal l 语句。
            • 编译器 -> 解释器?
            猜你喜欢
            • 2020-10-01
            • 1970-01-01
            • 2023-03-23
            • 2011-09-06
            • 2011-07-14
            • 2014-11-04
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多