提问人:helpermethod 提问时间:9/7/2010 最后编辑:vvvvvhelpermethod 更新时间:11/24/2023 访问量:109964
连接两个列表 - '+=' 和 extend() 之间的区别
Concatenating two lists - difference between '+=' and extend()
问:
我已经看到实际上有两种(也许更多)方法可以在 Python 中连接列表:
一种方法是使用以下方法:extend()
a = [1, 2]
b = [2, 3]
b.extend(a)
另一个使用加号 (+) 运算符:
b += a
现在我想知道:这两个选项中的哪一个是执行列表串联的“pythonic”方式,两者之间有区别吗?(我已经查找了官方的 Python 教程,但找不到有关此主题的任何内容)。
答:
字节码级别上的唯一区别是 .extend
方式涉及函数调用,这在 Python 中比 INPLACE_ADD
稍微贵一些。
这真的没什么你应该担心的,除非你执行这个操作数十亿次。然而,瓶颈很可能在其他地方。
评论
.__iadd__()
.__add__()
.__radd__()
.extend()
extend()
.extend
比 快。扩展具有额外的函数调用无关。 是一个运算符,它还会导致函数调用。之所以更快,是因为它所做的工作要少得多。 将 (1) 创建一个列表,从该列表中复制所有元素(引用),然后它将获取第二个列表并添加引用。 不会创建新列表,也不会从该列表中复制引用元素。extend 等效于 。extend 将对您正在执行操作的列表进行操作,并且应该使用而不是+
+
.extend
+
.extend
a[len(a):] = iterable
L = L + iterable
您不能将 += 用于非局部变量(对于函数来说不是局部变量,也不是全局变量)
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*
评论
您可以链接函数调用,但不能直接 += 函数调用:
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
我想说的是,numpy 有一些区别(我刚刚看到这个问题是关于连接两个列表的,而不是 numpy 数组,但由于这对初学者来说可能是个问题,比如我,我希望这可以帮助那些寻求本文解决方案的人),例如。
import numpy as np
a = np.zeros((4,4,4))
b = []
b += a
它将返回错误
ValueError:操作数不能与形状 (0,) 一起广播 (4,4,4)
b.extend(a)
完美工作
从 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;
}
根据 Python 进行数据分析。
“请注意,通过加法进行列表串联是一项相对昂贵的操作,因为必须创建一个新列表并复制对象。通常最好使用 extend 将元素追加到现有列表,尤其是在构建大型列表时。" 因此
everything = []
for chunk in list_of_lists:
everything.extend(chunk)
比 Concatenative 替代方案更快:
everything = []
for chunk in list_of_lists:
everything = everything + chunk
评论
everything = everything + temp
不一定以与 相同的方式实现。everything += temp
everything += temp
everything
列表上的 .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 在下面的评论很好,很清楚。
评论
list.extend(iterable) Extend the list by appending all the items from the iterable. Equivalent to a[len(a):] = iterable.
+=
list
numpy.ndarray
list
实际上,这三个选项之间存在差异:和 。前者总是较慢,而其他两个大致相同。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
评论
ADD
INPLACE_ADD
extend()
ADD
INPLACE_ADD
extend()
我查找了官方的 Python 教程,但找不到有关此主题的任何内容
此信息恰好隐藏在编程常见问题解答中:
...对于列表,[即 ] 等同于调用列表并返回列表。这就是为什么我们说对于列表,是
__iadd__
+=
extend
+=
list.extend
您也可以在 CPython 源代码中亲眼看到这一点:https://github.com/python/cpython/blob/v3.8.2/Objects/listobject.c#L1000-L1011
当列表位于元组中时,只能使用 .extend()
这将起作用
t = ([],[])
t[0].extend([1,2])
虽然这不会
t = ([],[])
t[0] += [1,2]
原因是生成一个新对象。如果你看一下长版本:+=
t[0] = t[0] + [1,2]
您可以看到这将如何更改元组中的对象,这是不可能的。using 修改元组中的对象,这是允许的。.extend()
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 做同样的事情。
评论
/list_plus.py
/list_extend.py
算子的速度可以忽略不计,如果比达隆索亚的回答所证实的要快的话。实际上,您正在将方法调用交换为另外两个操作。+=
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 的回答)。numpy
numpy
这不适用于元组中的列表,很可能是因为操作(Jann Poppinga 的答案)。但是请注意,在这种情况下(作为方法调用)工作得很好。+=
STORE_SUBSCR
list.__iadd__()
不会创建新列表(丁的答案)。+=
我很抱歉将所有这些作为答案发布,我没有足够的代表来发表评论。
评论
.__iadd__()
.__add__()
.__radd__()
.extend()