python列表连接中的奇怪行为

python列表连接中的奇怪行为,python,list,Python,List,我创建了一个Python列表,如下所示 >>> list1 = ['a', 'b', 'c'] 设定 >>> list2 = list1 现在,我对list1和list2执行两个类似的操作 >>> list1 = list1 + [1, 2, 3] >>> list1 ['a', 'b', 'c', 1, 2, 3] >>> list2 ['a', 'b', 'c'] 及 但两种情况下的结果都不同。原

我创建了一个Python列表,如下所示

>>> list1 = ['a', 'b', 'c']
设定

>>> list2 = list1
现在,我对
list1
list2
执行两个类似的操作

>>> list1 = list1 + [1, 2, 3]
>>> list1
['a', 'b', 'c', 1, 2, 3]
>>> list2
['a', 'b', 'c']


但两种情况下的结果都不同。原因是什么?

根据Ned Batcheld关于Pycon 2015的演讲:

列表上的
\uuuu iadd\uuuu
是作为扩展实际实例实现的(根据Ned,它的CPython的非文档化行为)。在
list1=list2
之后,两个名称都引用同一个实例-因此扩展实例在第二个名称下可见

\uuuu添加\uuuu
实际上是基于两个输入列表创建一个新列表

作为一个证明,考虑下面的代码片段:

import dis

def f1():
    list1 += [1,2,3]

def f2():
    list1 = list1 + [1,2,3]

dis.dis(f1)
dis.dis(f2)
让我们检查输出:

>>> dis.dis(f1)
  2           0 LOAD_FAST                0 (list1)
              3 LOAD_CONST               1 (1)
              6 LOAD_CONST               2 (2)
              9 LOAD_CONST               3 (3)
             12 BUILD_LIST               3
             15 INPLACE_ADD
             16 STORE_FAST               0 (list1)
             19 LOAD_CONST               0 (None)
             22 RETURN_VALUE
>>> dis.dis(f2)
  2           0 LOAD_FAST                0 (list1)
              3 LOAD_CONST               1 (1)
              6 LOAD_CONST               2 (2)
              9 LOAD_CONST               3 (3)
             12 BUILD_LIST               3
             15 BINARY_ADD
             16 STORE_FAST               0 (list1)
             19 LOAD_CONST               0 (None)
             22 RETURN_VALUE

如您所见,
+=
使用了
INPLACE\u ADD
,而
l1+l2
没有使用。

Python中的列表操作符实际上在内部调用了
list.extend()
函数,因此列表被就地扩展


然而,当我们使用
+
串联运算符时,会创建并返回一个新的列表,因此
list1
中的实际列表不会更改,而是
list1
现在指向一个新的列表。

这背后的原因是
+=
+
调用类的两个不同方法,和

从API的角度来看,iadd应该用于修改原地可变的对象(返回突变的对象),而add应该返回某个对象的新实例。对于不可变对象,这两个方法都返回一个新实例,但iadd将使用旧实例的相同名称将新实例放在当前命名空间中。这就是为什么

i = 1
i += 1
似乎是我。实际上,您得到一个新的整数,并将其赋值为“在”i之上——丢失对旧整数的一个引用。在这种情况下,i+=1与i=i+1完全相同。但是,对于大多数可变对象,情况就不同了:

作为一个具体例子:

a = [1, 2, 3]
b = a
b += [1, 2, 3]
print a  #[1, 2, 3, 1, 2, 3]
print b  #[1, 2, 3, 1, 2, 3]
与之相比:

a = [1, 2, 3]
b = a
b = b + [1, 2, 3]
print a #[1, 2, 3]
print b #[1, 2, 3, 1, 2, 3]

注意在第一个示例中,由于b和a引用同一个对象,当我在b上使用+=时,它实际上改变了b(a也看到了这种改变——毕竟,它引用的是同一个列表)。然而,在第二种情况下,当我执行b=b+[1,2,3]时,这将获取b正在引用的列表,并将其与新列表[1,2,3]连接起来。然后,它将连接的列表存储在当前名称空间中为b,而不考虑b之前是哪一行。

这是因为在第一次操作中将一个新对象分配给
list1
,而在第二次操作中将分配给
list2
的原始对象更改

如果使用
id()

>>> list1 = ['a', 'b', 'c']
>>> id(list1)
4394813200
>>> list2 = list1
>>> id(list2)
4394813200 # same id
>>> list1 = list1 + [1, 2, 3]
>>> id(list1)
4394988392 # list1 now references another object
>>> list1
['a', 'b', 'c', 1, 2, 3]
>>> id(list2)
4394813200 # list2 still references the old one
>>> list2
['a', 'b', 'c']
>>> list2 += [1,2,3]
>>> id(list2)
4394813200 # list2 still references the old one
>>> list2
['a', 'b', 'c', 1, 2, 3]
>>> id(list1)
4394988392
>>> list1
['a', 'b', 'c', 1, 2, 3]

试问一下,list1和list2上的操作不都假设执行相同的作业吗?它不是已经做到了吗?区别在哪里?
>>> list1 = ['a', 'b', 'c']
>>> id(list1)
4394813200
>>> list2 = list1
>>> id(list2)
4394813200 # same id
>>> list1 = list1 + [1, 2, 3]
>>> id(list1)
4394988392 # list1 now references another object
>>> list1
['a', 'b', 'c', 1, 2, 3]
>>> id(list2)
4394813200 # list2 still references the old one
>>> list2
['a', 'b', 'c']
>>> list2 += [1,2,3]
>>> id(list2)
4394813200 # list2 still references the old one
>>> list2
['a', 'b', 'c', 1, 2, 3]
>>> id(list1)
4394988392
>>> list1
['a', 'b', 'c', 1, 2, 3]