附加到元组中定义的列表 - 这是一个错误吗? [复制]
Posted
技术标签:
【中文标题】附加到元组中定义的列表 - 这是一个错误吗? [复制]【英文标题】:Append to a list defined in a tuple - is it a bug? [duplicate] 【发布时间】:2015-06-27 03:20:50 【问题描述】:所以我有这个代码:
tup = ([1,2,3],[7,8,9])
tup[0] += (4,5,6)
产生此错误:
TypeError: 'tuple' object does not support item assignment
这段代码:
tup = ([1,2,3],[7,8,9])
try:
tup[0] += (4,5,6)
except TypeError:
print tup
打印这个:
([1, 2, 3, 4, 5, 6], [7, 8, 9])
这是预期的行为吗?
注意
我意识到这不是一个非常常见的用例。然而,虽然错误是意料之中的,但我没想到列表会发生变化。
【问题讨论】:
事实上,您的第一次尝试显示错误但有效:>>> tup[0]
向我返回 [1, 2, 3, 4, 5, 6]
。
重新编辑:这可能不是一个常见的用例,但了解其原因非常重要。
【参考方案1】:
是的,这是预期的。
元组不能更改。元组和列表一样,是指向其他对象的结构。它不关心那些对象是什么。它们可以是字符串、数字、元组、列表或其他对象。
因此,对元组中包含的一个对象执行任何操作,包括附加到该对象(如果它是一个列表)与元组的语义无关。
(想象一下,如果您编写了一个类,该类具有导致其内部状态发生变化的方法。您不会期望根据对象的存储位置调用这些方法是不可能的)。
或者另一个例子:
>>> l1 = [1, 2, 3]
>>> l2 = [4, 5, 6]
>>> t = (l1, l2)
>>> l3 = [l1, l2]
>>> l3[1].append(7)
由一个列表和一个元组引用的两个可变列表。我应该能够做最后一行吗(答案:是的)。如果你认为答案是否定的,为什么不呢? t
是否应该更改 l3
的语义(答案:否)。
如果你想要一个顺序结构的不可变对象,它应该是元组。
为什么会出错?
此示例使用中缀运算符:
许多操作都有“就地”版本。以下功能 提供比通常更原始的就地操作符访问 语法可以;例如,语句 x += y 等价于 x = 运算符.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)
等价于:
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
__iadd__
行成功,并修改了第一个列表。所以名单已经改变。 __iadd__
调用返回变异列表。
第二行尝试将列表分配回元组,但失败了。
因此,在程序结束时,列表已扩展,但+=
操作的第二部分失败。具体见this question。
【讨论】:
元组一直向下;很好…… 那为什么会出错,什么时候工作呢? @Pureferret:它“有效”,因为在列表上调用__iadd__
成功地改变了列表。但是将变异列表分配回元组的步骤失败了。看起来它成功了,因为元组已经包含对列表的引用。
@StevenRumbalski 啊,列表是一个视图,不需要重新分配,但无论如何都发生了(导致错误)。
我认为您有错字tup0] = x
您可能希望在下一个版本中更正【参考方案2】:
我猜tup[0] += (4, 5, 6)
被翻译成:
tup[0] = tup[0].__iadd__((4,5,6))
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
的位置。这失败了,因为元组不能被修改。【参考方案3】:
元组不能直接更改,正确。但是,您可以通过引用更改元组的元素。喜欢:
>>> tup = ([1,2,3],[7,8,9])
>>> l = tup[0]
>>> l += (4,5,6)
>>> tup
([1, 2, 3, 4, 5, 6], [7, 8, 9])
【讨论】:
【参考方案4】:Python 开发人员写了一个官方解释为什么会在这里发生:https://docs.python.org/2/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works
简短的版本是 += 实际上做了两件事,一件接一件:
-
运行右边的东西。
将结果赋值给左边的变量
在这种情况下,第 1 步有效,因为您可以将内容添加到列表(它们是可变的),但第 2 步失败,因为您无法在创建后将内容放入元组(元组是不可变的)。
在实际程序中,我建议您不要使用 try-except 子句,因为 tup[0].extend([4,5,6])
的作用完全相同。
【讨论】:
以上是关于附加到元组中定义的列表 - 这是一个错误吗? [复制]的主要内容,如果未能解决你的问题,请参考以下文章