Python中的LIST.append(1)和LIST = LIST + [1]有什么区别?

8
当我执行以下语句时(我使用交互式 shell),会得到以下结果:
L=[1,2,3]
K=L

L.append(4)

L
[1,2,3,4]
K
[1,2,3,4]

但是当我用 L=L+[4] 替换 L.append(4) 时,会得到以下结果:
L
[1,2,3,4]
K
[1,2,3]

这是一种引用吗?为什么会发生这种情况?

我注意到的另一个有趣的事情是,L+=[4] 的行为类似于 .append,这有点奇怪,因为我认为它应该像 L = L + [4] 那样。

如果能对此进行澄清将不胜感激。

谢谢。


在Python中,+=很奇怪。例如,a = (1, 2); a += (2,)会得到(1, 2, 3)!这正好与列表的情况相反,它会就地修改列表。没有办法就地修改元组。这就是为什么很多人更喜欢始终使用a = a + b形式的原因。 - aaronasterling
不,它并没有。在执行a = (1, 2)之后;再执行a += (2,),此时a的值为(1,2,2),这有什么奇怪的呢? - Kugel
我认为他的意思是 a += (3,)。而且这样允许你就地修改一个(不可变的)元组,有点奇怪。 - efritz
1
@Kugel,没错。这是我的笔误。奇怪的是它适用于列表但不适用于元组。+=运算符没有统一的行为。在某些情况下,它等同于a = a + b,而在其他情况下(列表)则完全不同。在大多数语言中,它总是等同于a = a + b - aaronasterling
这是很有道理的,+= 不能在每种对象类型上都是一致的。这里一致的是不可变对象不会被修改,可变对象会被修改。 - Kugel
4个回答

16
L.append(4)

这将在现有列表L的末尾添加一个元素。

L += [4]
< p > += 运算符调用了魔术方法 __iadd__()。实际上,list 重写了 __iadd__() 方法,并使其等价于 extend() 方法,类似于 append() 方法,将元素直接添加到现有列表中。

L = L + [4]

L + [4] 生成一个新的列表,该列表等于在 L 末尾加上4。然后将这个新的列表赋值回L。因为你创建了一个新的列表对象,所以此赋值对于K是不影响的。

我们可以使用id()来确定何时创建了一个新的对象引用:

>>> L = [1, 2, 3]
>>> id(L)
152678284
>>> L.append(4)
>>> id(L)
152678284

>>> L = [1, 2, 3]
>>> id(L)
152680524
>>> L = L + [4]
>>> id(L)
152678316

2
使用`append`直接修改列表。使用`L=L+[4]`,您将复制原始的 `L` 并添加一个新元素,然后将该结果分配回 `L` 并打破其与 `K` 的等同性。关于`+=`的行为我不太确定。

+= 似乎像 append 一样工作(我刚测试了一下) - Andre Holzner
1
@Andre,不是的。它的行为类似于“extend”。 - habnabit

1
如果你对字节码感到好奇:
>>> def L_app( ):
...     L.append( 4 )
...
>>> def L_add( ):
...     L = L + [ 4 ]
...
>>> def L_add_inplace( ):
...     L += [ 4 ]
...
>>> dis.dis( L_app )
  2           0 LOAD_GLOBAL              0 (L)
              3 LOAD_ATTR                1 (append)
              6 LOAD_CONST               1 (4)
              9 CALL_FUNCTION            1
             12 POP_TOP
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE
>>> dis.dis( L_add )
  2           0 LOAD_FAST                0 (L)
              3 LOAD_CONST               1 (4)
              6 BUILD_LIST               1
              9 BINARY_ADD
             10 STORE_FAST               0 (L)
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE
>>> dis.dis( L_add_inplace )
  2           0 LOAD_FAST                0 (L)
              3 LOAD_CONST               1 (4)
              6 BUILD_LIST               1
              9 INPLACE_ADD
             10 STORE_FAST               0 (L)
             13 LOAD_CONST               0 (None)
             16 RETURN_VALUE

最有趣的情况是 += 形式。 - aaronasterling
虽然我对编程并不陌生,但是对于Python还是相当新手。这些(字节码)实在让我感到困惑。难道字节码不就是比编程语言更低级、更接近机器码且执行速度更快的代码吗?也许我理解错了定义。 - aitee
@aaronasterling:确实很有趣!已添加。@aitee:是的,它们是。dis会打印出Python函数编译成的字节码的“好”表示形式。这通常对于查看“引擎盖下”的情况非常有用。 - Katriel

0
在你的第一个例子中,KL变量名引用同一对象,因此当你调用改变该对象的方法时,这些变化显然会通过两个引用看到。在第二个例子中,+运算符调用list.__add__,它返回一个新对象(两个列表的连接),而L名称现在引用这个新对象,而K则保持不变。

所以,当我将一个变量分配给列表时,它会得到对对象的引用?这是因为实际上列表不是列表,而是同样的引用吗?我想我现在明白了。这可能会发生在列表和字典中,对吗?因为它们实际上都是对对象本身的引用。非常感谢您的澄清。我在其他地方读到的东西很模糊,没有真正说明使用+运算符调用了list.__add__函数。谢谢 (^_^) - aitee
你对 += 有什么想法吗? - aitee
在Python中,不仅listdict所有东西都是对象。所有对象都存储在堆中,所有变量只是引用。但是有些对象是不可变的(例如intstring),而且一些不可变对象是内部化的,因此您不会遇到这种令人困惑的行为。您必须区分修改数据和修改变量名称与数据之间的映射。后者是当您将某些内容分配给变量时发生的情况。但请注意,如果变量映射表示对象的成员,则变量映射本身可以是数据的一部分。 - rkhayrov
关于 += 运算符:这只是调用修改对象的 __iadd__ 方法的语法糖。 - rkhayrov
谢谢,这些都非常有帮助。 - aitee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接