我看到在Python中实际上有两种(可能更多)连接列表的方法:
一种方法是使用extend()
方法:
a = [1, 2]
b = [2, 3]
b.extend(a)
另一种方法是使用加号 (+) 运算符:
b += a
现在我想知道:这两种选项中哪一种是“Pythonic”(符合 Python 语言特性的)的列表连接方式,它们之间是否有区别?(我已经查阅了官方 Python 教程,但没有找到任何关于这个话题的内容。)
唯一的字节码级别上的区别是,.extend
方法涉及一个函数调用,在Python中比INPLACE_ADD
稍微昂贵一些。
这并不是你应该担心的事情,除非你需要执行数十亿次此操作。然而,瓶颈很可能出现在其他地方。
.__iadd__()
/.__add__()
/.__radd__()
与 .extend()
这些方法方面,差异具有更多的含义。 - Nick Textend()
而不是操作符。“数十亿次操作”的用例是一个有效的观点,但在我的职业生涯中,我没有遇到过这种情况超过几次。 - void.pointer.extend
is faster than +
. There is nothing to do with extend having an extra function call. +
is an operator and it also causes a function call. The reason .extend
is faster is because it does much less work. +
will (1) create a list, copy all elements (references) from that list, then it will get the second list and add the references. .extend
will not create a new list nor copy references elements from that list. extend is equivalent to a[len(a):] = iterable
. extend will operate over the list you are doing the operation and should be used instead of L = L + iterable
- fsan对于非局部变量(即对于函数不是本地的且也不是全局的变量),您不能使用 +=。
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*
的错误。
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
会返回错误信息
值错误:无法将形状为(0,)的操作数广播到形状为(4,4,4)的操作数上
b.extend(a)
可以完美地工作
实际上,这三个选项之间存在差异: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()
的原地操作慢。 - wombatonfireary += ext 会创建一个新的列表对象,并将列表 "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
的内容? - Niko Pasanen列表上的 .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)
,通过添加可迭代对象中的所有项来扩展列表。相当于a[len(a):] = iterable
。我猜我自己回答了我的星号问题。" - grofte+=
运算符与不同类型的对象(与问题中的两个列表相反)时,你不能期望得到对象的连接。你也不能期望会返回一个list
类型。看一下你的代码,你得到了一个numpy.ndarray
而不是list
。 - wombatonfire没有太大的区别。
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;
}
__iadd__
[即+=
]等同于在列表上调用extend
并返回列表。这就是为什么我们说对于列表,+=
是list.extend
的“简写”的原因。这将起作用。
t = ([],[])
t[0].extend([1,2])
虽然这不会
t = ([],[])
t[0] += [1,2]
+=
会生成一个新的对象。如果你看长版本的代码:t[0] = t[0] + [1,2]
你可以看到这会改变元组中的对象,这是不可能的。使用.extend()
修改了元组中的一个对象,这是被允许的。
.__iadd__()
/.__add__()
/.__radd__()
与.extend()
之间的区别会更加明显。 - Nick T