附加到元组中定义的列表 - 这是一个错误吗?

ida*_*hmu 37 python tuples list

所以我有这个代码:

tup = ([1,2,3],[7,8,9])
tup[0] += (4,5,6)
Run Code Online (Sandbox Code Playgroud)

这会产生此错误:

TypeError: 'tuple' object does not support item assignment
Run Code Online (Sandbox Code Playgroud)

虽然这段代码:

tup = ([1,2,3],[7,8,9])
try:
    tup[0] += (4,5,6)
except TypeError:
    print tup
Run Code Online (Sandbox Code Playgroud)

打印这个:

([1, 2, 3, 4, 5, 6], [7, 8, 9])
Run Code Online (Sandbox Code Playgroud)

这种行为有望吗?

注意

我意识到这不是一个非常常见的用例.但是,虽然预计会出现错误,但我没想到列表会发生变化.

Joe*_*Joe 35

是的,这是预期的.

元组不能改变.像列表一样的元组是指向其他对象的结构.它并不关心那些物体是什么.它们可以是字符串,数字,元组,列表或其他对象.

因此,对元组中包含的一个对象执行任何操作,包括如果它是列表,则附加到该对象,与元组的语义无关.

(想象一下,如果你写了一个类,它上面有导致其内部状态改变的方法.你不会指望根据它存储的位置调用这些方法是不可能的).

或者另一个例子:

>>> l1 = [1, 2, 3]
>>> l2 = [4, 5, 6]
>>> t = (l1, l2)
>>> l3 = [l1, l2]
>>> l3[1].append(7)
Run Code Online (Sandbox Code Playgroud)

由列表和元组引用的两个可变列表.我应该能够做最后一行(回答:是).如果您认为答案是否定的,为什么不呢?应该t改变l3(答案:否)的语义.

如果你想要一个连续结构的不可变对象,那么它应该是元组一直向下.

为什么会出错?

此示例使用中缀运算符:

许多操作都有"就地"版本.与通常的语法相比,以下函数提供了对原位运算符的更原始的访问; 例如,语句x + = y等于x = operator.iadd(x,y).另一种说法是说z = operator.iadd(x,y)相当于复合语句z = x; z + = y.

https://docs.python.org/2/library/operator.html

所以这:

l = [1, 2, 3]
tup = (l,)
tup[0] += (4,5,6)
Run Code Online (Sandbox Code Playgroud)

相当于:

l = [1, 2, 3]
tup = (l,)
x = tup[0]
x = x.__iadd__([4, 5, 6]) # like extend, but returns x instead of None
tup[0] = x
Run Code Online (Sandbox Code Playgroud)

__iadd__行成功,并修改第一个列表.所以列表已经改变了.该__iadd__调用返回变异列表.

第二行尝试将列表分配回元组,这会失败.

因此,在程序结束时,列表已被扩展,但操作的第二部分+=失败.有关细节,请参阅此问题.

  • 一直都是元组; 很好... (4认同)
  • @Pureferret:它"有效",因为在列表上调用`__iadd__`会成功改变列表.但是将变异列表分配回元组的步骤失败了.看起来它成功了,因为元组已经包含对列表的引用. (2认同)
  • @StevenRumbalski啊,列表是一个视图,并没有"需要"重新分配,但无论如何发生了(导致错误). (2认同)

Jun*_*sor 12

嗯,我想这tup[0] += (4, 5, 6)被翻译成:

tup[0] = tup[0].__iadd__((4,5,6))
Run Code Online (Sandbox Code Playgroud)

tup[0].__iadd__((4,5,6))通常在第一个元素中更改列表.但由于元组是不可变的,因此赋值失败.

  • 这绝对是正确的.我们可以通过使用`dis`模块看到它.给定`def f(tup):tup [0] + =(4,5,6)`.如果我们使用`dis.dis(f)`.字节码中包含INPLACE_ADD和STORE_SUBSCR.INPLACE_ADD对应于`obj .__ iadd __((4,5,6))`.这个变异步骤成功了.STORE_SUBSCR是将结果分配给元组`tup [0] = result_of_idadd`的位置.这会失败,因为无法修改元组. (2认同)

a5k*_*kin 5

元组不能直接改变,正确.但是,您可以通过引用更改元组的元素.喜欢:

>>> tup = ([1,2,3],[7,8,9])
>>> l = tup[0]
>>> l += (4,5,6)
>>> tup
([1, 2, 3, 4, 5, 6], [7, 8, 9])
Run Code Online (Sandbox Code Playgroud)