python列表连接中的奇怪行为

时间:2015-06-29 15:49:59

标签: python list

我创建了一个Python列表

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

并设置

>>> list2 = list1

现在我对list1list2

执行两项类似的操作
>>> list1 = list1 + [1, 2, 3]
>>> list1
['a', 'b', 'c', 1, 2, 3]
>>> list2
['a', 'b', 'c']

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

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

4 个答案:

答案 0 :(得分:2)

Python中用于列表的+=运算符实际上是在内部调用list.extend()函数,因此列表已扩展到位。

当我们执行+连接运算符时,会创建并返回一个新列表,因此list1中的实际列表不会更改,而list1现在指向一个新的清单。

答案 1 :(得分:1)

基于Ned Batchelder关于Pycon 2015的演讲:

列表中的

__iadd__实现为扩展实际实例(根据Ned,它是CPython的非记录行为)。在list1 = list2之后,两个名称都引用相同的实例 - 因此在第二个名称下可以看到扩展实例。

__add__实际上是根据两个输入列表创建一个新列表。

作为证明,请考虑以下代码段:

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_ADD,其中l1 + l2没有。{/ p>

答案 2 :(得分:1)

这背后的原因是+=+调用了该类的两种不同方法,__iadd__ method__add__ method

从API的角度来看, iadd 应该用于修改可变对象(返回已变异的对象),而添加应该返回一个新的实例一些东西。对于不可变对象,两个方法都返回一个新实例,但 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和引用相同的对象,当我在b上使用+ =时,它实际上改变了b(并且看到了这个改变 - 毕竟,它引用了相同的列表)。然而,在第二种情况下,当我执行b = b + [1,2,3]时,这将采用b引用的列表并将其与新列表[1,2,3]连接。然后它将连接列表存储在当前命名空间中作为b - 不考虑之前的行是什么。

答案 3 :(得分:1)

这是因为您正在使用第一个操作向list1分配新对象,而您正在使用第二个操作更改分配给list2的原始对象。

如果您使用id()检查分配给变量的对象的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]