为什么改变元组中的列表会引发异常但无论如何都会改变它?
Why does mutating a list in a tuple raise an exception but mutate it anyway?
我不确定我是否完全理解下面的小片段(在 Py v3.6.7 上)中发生的事情。如果有人能向我解释我们如何成功地改变列表,即使 Python.
抛出错误,那就太好了
我知道我们可以改变一个列表并更新它,但是错误是怎么回事?就像我的印象是,如果有错误,那么 x
应该保持不变。
x = ([1, 2], )
x[0] += [3,4] # ------ (1)
第 (1) 行抛出的 Traceback 是
> TypeError: 'tuple' object doesn't support item assignment..
我明白错误的含义,但我无法了解它的上下文。
但现在如果我尝试打印我的变量 x
的值,Python 说是,
print(x) # returns ([1, 2, 3, 4])
据我所知,在 Python 允许列表发生变化之后发生异常,然后希望它尝试重新分配它。我认为它在那里爆炸,因为元组是不可变的。
谁能解释一下幕后发生的事情?
编辑 - 1
来自 ipython 控制台的错误图像;
我的直觉是 x[0] += [3, 4]
行首先修改列表本身,因此 [1, 2]
变为 [1, 2, 3, 4]
, 然后 它试图调整抛出 TypeError
的元组的内容,但元组始终指向同一个列表,因此当指向 的对象是 [=23= 时,其内容(根据指针)不会被修改]修改。
我们可以这样验证:
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], )
尽管存储在“不可变”元组中,但它不会引发错误并且会在适当的位置进行修改。
这里发生了一些事情。
+=
并不总是 +
然后 =
.
如果需要,+=
和 +
可以有不同的实现。
看看这个例子。
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)
同样,列表 class 对 +
和 +=
有单独的实现。
使用+=
实际上在后台执行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
使用 +
创建一个新列表。
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
现在让我们来回答你的问题。
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])
+
运算符在这里首先执行,这意味着列表得到更新(扩展)。这是允许的,因为对列表的引用(存储在元组中的值)不会改变,所以这很好。
然后 =
尝试更新 tuple
中的引用,这是不允许的,因为元组是不可变的。
但实际列表被 +
变异了。
Python 无法更新对元组内列表的引用,但由于它会更新为相同的引用,我们作为用户看不到更改。
因此,+
被执行,而 =
执行失败。 +
对 tuple
中已经引用的 list
进行了变异,因此我们在列表中看到了变异。
现有答案是正确的,但我认为文档实际上可以对此提供一些额外的说明:
来自 in-place operators documentation:
the statement x += y is equivalent to x = operator.iadd(x, y)
所以当我们写
x[0] += [3, 4]
相当于
x[0] = operator.iadd(x[0], [3, 4])
iadd
是使用 extend
实现的,在列表的情况下,所以我们看到这个操作实际上做了两件事:
- 扩展列表
- 重新分配给索引 0
如文档后面所述:
note that when an in-place method is called, the computation and assignment are performed in two separate steps.
第一次操作没有问题
第二个操作是不可能的,因为x
是一个元组。
但为什么要重新分配?
在这种情况下,这似乎令人费解,人们可能想知道为什么 +=
运算符等同于 x = operator.iadd(x, y)
,而不是简单地 operator.iadd(x, y)
.
这不适用于不可变类型,例如 int 和 str。因此,虽然 iadd
对于列表实现为 return x.extend(y)
,但对于整数实现为 return x + y
。
再次来自文档:
For immutable targets such as strings, numbers, and tuples, the updated value is computed, but not assigned back to the input variable
我不确定我是否完全理解下面的小片段(在 Py v3.6.7 上)中发生的事情。如果有人能向我解释我们如何成功地改变列表,即使 Python.
抛出错误,那就太好了我知道我们可以改变一个列表并更新它,但是错误是怎么回事?就像我的印象是,如果有错误,那么 x
应该保持不变。
x = ([1, 2], )
x[0] += [3,4] # ------ (1)
第 (1) 行抛出的 Traceback 是
> TypeError: 'tuple' object doesn't support item assignment..
我明白错误的含义,但我无法了解它的上下文。
但现在如果我尝试打印我的变量 x
的值,Python 说是,
print(x) # returns ([1, 2, 3, 4])
据我所知,在 Python 允许列表发生变化之后发生异常,然后希望它尝试重新分配它。我认为它在那里爆炸,因为元组是不可变的。
谁能解释一下幕后发生的事情?
编辑 - 1 来自 ipython 控制台的错误图像;
我的直觉是 x[0] += [3, 4]
行首先修改列表本身,因此 [1, 2]
变为 [1, 2, 3, 4]
, 然后 它试图调整抛出 TypeError
的元组的内容,但元组始终指向同一个列表,因此当指向 的对象是 [=23= 时,其内容(根据指针)不会被修改]修改。
我们可以这样验证:
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], )
尽管存储在“不可变”元组中,但它不会引发错误并且会在适当的位置进行修改。
这里发生了一些事情。
+=
并不总是 +
然后 =
.
+=
和 +
可以有不同的实现。
看看这个例子。
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)
同样,列表 class 对 +
和 +=
有单独的实现。
使用+=
实际上在后台执行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
使用 +
创建一个新列表。
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
现在让我们来回答你的问题。
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])
+
运算符在这里首先执行,这意味着列表得到更新(扩展)。这是允许的,因为对列表的引用(存储在元组中的值)不会改变,所以这很好。
然后 =
尝试更新 tuple
中的引用,这是不允许的,因为元组是不可变的。
但实际列表被 +
变异了。
Python 无法更新对元组内列表的引用,但由于它会更新为相同的引用,我们作为用户看不到更改。
因此,+
被执行,而 =
执行失败。 +
对 tuple
中已经引用的 list
进行了变异,因此我们在列表中看到了变异。
现有答案是正确的,但我认为文档实际上可以对此提供一些额外的说明:
来自 in-place operators documentation:
the statement x += y is equivalent to x = operator.iadd(x, y)
所以当我们写
x[0] += [3, 4]
相当于
x[0] = operator.iadd(x[0], [3, 4])
iadd
是使用 extend
实现的,在列表的情况下,所以我们看到这个操作实际上做了两件事:
- 扩展列表
- 重新分配给索引 0
如文档后面所述:
note that when an in-place method is called, the computation and assignment are performed in two separate steps.
第一次操作没有问题
第二个操作是不可能的,因为x
是一个元组。
但为什么要重新分配?
在这种情况下,这似乎令人费解,人们可能想知道为什么 +=
运算符等同于 x = operator.iadd(x, y)
,而不是简单地 operator.iadd(x, y)
.
这不适用于不可变类型,例如 int 和 str。因此,虽然 iadd
对于列表实现为 return x.extend(y)
,但对于整数实现为 return x + y
。
再次来自文档:
For immutable targets such as strings, numbers, and tuples, the updated value is computed, but not assigned back to the input variable