Adi*_*tya 15 python tuples immutability python-3.x
我不确定我是否完全理解下面的小片段(在 Py v3.6.7 上)中发生的事情。如果有人能向我解释我们如何成功地改变列表,即使 Python 抛出了一个错误,那就太好了。
我知道我们可以改变一个列表并更新它,但是错误是什么?就像我的印象是,如果有错误,那么x应该保持不变。
x = ([1, 2], )
x[0] += [3,4] # ------ (1)
Run Code Online (Sandbox Code Playgroud)
第 (1) 行抛出的 Traceback 是
> TypeError: 'tuple' object doesn't support item assignment..
Run Code Online (Sandbox Code Playgroud)
我明白错误意味着什么,但我无法获得它的上下文。
但是现在如果我尝试打印变量的值x,Python 会说它是,
> TypeError: 'tuple' object doesn't support item assignment..
Run Code Online (Sandbox Code Playgroud)
据我所知,异常发生在 Python 允许列表的突变发生之后,然后希望它尝试重新分配它。我认为它吹到了那里,因为元组是不可变的。
有人可以解释一下引擎盖下发生了什么吗?
编辑 - 1 错误来自 ipython 控制台作为图像;
我的直觉是,该行x[0] += [3, 4]首先修改列表本身,因此[1, 2]变为[1, 2, 3, 4],然后它尝试调整抛出 a 的元组的内容TypeError,但元组始终指向同一个列表,因此其内容(就指针而言)不会被修改而指向的对象被修改。
我们可以这样验证:
a_list = [1, 2, 3]
a_tuple = (a_list,)
print(a_tuple)
>>> ([1, 2, 3],)
a_list.append(4)
print(a_tuple)
>>> ([1, 2, 3, 4], )
Run Code Online (Sandbox Code Playgroud)
尽管存储在“不可变”元组中,但这不会引发错误并会在适当的位置对其进行修改。
这里发生了一些事情。
+=不总是+然后=。
+=+如果需要,可以有不同的实现。
看看这个例子。
In [13]: class Foo:
...: def __init__(self, x=0):
...: self.x = x
...: def __add__(self, other):
...: print('+ operator used')
...: return Foo(self.x + other.x)
...: def __iadd__(self, other):
...: print('+= operator used')
...: self.x += other.x
...: return self
...: def __repr__(self):
...: return f'Foo(x={self.x})'
...:
In [14]: f1 = Foo(10)
In [15]: f2 = Foo(20)
In [16]: f3 = f1 + f2
+ operator used
In [17]: f3
Out[17]: Foo(x=30)
In [18]: f1
Out[18]: Foo(x=10)
In [19]: f2
Out[19]: Foo(x=20)
In [20]: f1 += f2
+= operator used
In [21]: f1
Out[21]: Foo(x=30)
Run Code Online (Sandbox Code Playgroud)
类似地,列表类对+和有单独的实现+=。
Using+=实际上是extend在后台进行操作。
In [24]: l = [1, 2, 3, 4]
In [25]: l
Out[25]: [1, 2, 3, 4]
In [26]: id(l)
Out[26]: 140009508733504
In [27]: l += [5, 6, 7]
In [28]: l
Out[28]: [1, 2, 3, 4, 5, 6, 7]
In [29]: id(l)
Out[29]: 140009508733504
Run Code Online (Sandbox Code Playgroud)
使用+创建一个新列表。
In [31]: l
Out[31]: [1, 2, 3]
In [32]: id(l)
Out[32]: 140009508718080
In [33]: l = l + [4, 5, 6]
In [34]: l
Out[34]: [1, 2, 3, 4, 5, 6]
In [35]: id(l)
Out[35]: 140009506500096
Run Code Online (Sandbox Code Playgroud)
现在让我们来回答你的问题。
In [36]: t = ([1, 2], [3, 4])
In [37]: t[0] += [10, 20]
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-37-5d9a81f4e947> in <module>
----> 1 t[0] += [10, 20]
TypeError: 'tuple' object does not support item assignment
In [38]: t
Out[38]: ([1, 2, 10, 20], [3, 4])
Run Code Online (Sandbox Code Playgroud)
该+操作被执行先到这里,这意味着该列表被更新(延长)。这是允许的,因为对列表的引用(存储在元组中的值)不会改变,所以这很好。
所述=然后尝试更新内部的参考tuple这是不允许的,因为元组是不可变的。
但实际列表被+.
Python 无法更新对元组内列表的引用,但由于它会被更新为相同的引用,我们作为用户看不到更改。
因此,+执行和=失败执行。+改变已经被引用的list内部,tuple所以我们在列表中看到了改变。
现有答案是正确的,但我认为文档实际上可以对此提供一些额外的了解:
来自就地运营商文档:
语句 x += y 等价于 x = operator.iadd(x, y)
所以当我们写
x[0] += [3, 4]
Run Code Online (Sandbox Code Playgroud)
它相当于
x[0] = operator.iadd(x[0], [3, 4])
Run Code Online (Sandbox Code Playgroud)
iaddextend在列表的情况下是使用,实现的,所以我们看到这个操作实际上做了两件事:
如文档后面所述:
请注意,当调用就地方法时,计算和赋值分两个单独的步骤执行。
第一次操作没有问题
第二个操作是不可能的,因为它x是一个元组。
在这种情况下,这似乎令人费解,人们可能想知道为什么+=运算符等效于x = operator.iadd(x, y),而不是简单的operator.iadd(x, y)。
这不适用于不可变类型,例如 int 和 str。因此,尽管iadd作为实施return x.extend(y)的名单,它是实现return x + y了整数。
再次来自文档:
对于不可变目标,例如字符串、数字和元组,会计算更新的值,但不会将其分配回输入变量