我不确定我是否完全理解下面的迷你代码片段(在Py v3.6.7上)发生了什么。即使有人向我解释一下如何成功更改列表(即使Python引发了错误),也很棒。
我知道我们可以更改列表并对其进行更新,但是出现了什么错误?就像我给人的印象是,如果出现错误,则x
应该保持不变。
x = ([1, 2], )
x[0] += [3,4] # ------ (1)
在第(1)行中引发的回溯是
> TypeError: 'tuple' object doesn't support item assignment..
我了解错误的含义,但无法获取该错误的上下文。
但是现在,如果我尝试打印变量x
的值,Python会说是,
print(x) # returns ([1, 2, 3, 4])
据我所知,Python允许列表发生突变后发生了异常,然后希望它尝试将其重新分配回去。我认为元组是不可变的。
有人可以解释幕后发生的事情吗?
编辑-1 来自ipython控制台的图像错误;
答案 0 :(得分:9)
我的直觉是,行x[0] += [3, 4]
首先会修改列表本身,因此[1, 2]
会变成[1, 2, 3, 4]
,然后然后尝试调整元组的内容会抛出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], )
即使存储在“不可变”元组中,它也不会引发错误并会对其进行修改。
答案 1 :(得分:9)
这里发生了一些事情。
+=
并不总是+
,然后总是=
。
+=
和+
可以有不同的实现方式。
看看这个例子。
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)
类似地,列表类具有+
和+=
的单独实现。
实际上,使用+=
在后台执行了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无法更新元组中列表的引用,但由于它会被更新为相同的引用,因此,由于用户看不到更改,因此我们。
因此,+
被执行而=
无法执行。 +
对list
内已经引用的tuple
进行了突变,因此我们在列表中看到了该突变。
答案 2 :(得分:5)
现有答案是正确的,但是我认为文档实际上可以对此提供一些额外的启示:
来自in-place operators documentation:
x + = y语句等同于x = operator.iadd(x,y)
所以当我们写
x[0] += [3, 4]
等效于
x[0] = operator.iadd(x[0], [3, 4])
在列表的情况下, iadd
是使用extend
实现的,因此我们看到此操作实际上在做两件事:
如本文档后面所述:
请注意,当调用就位方法时,计算和分配是在两个单独的步骤中进行的。
第一次操作没问题
第二个操作是不可能的,因为x
是一个元组。
在这种情况下,这似乎令人困惑,并且可能想知道为什么+=
运算符等效于x = operator.iadd(x, y)
而不是简单的operator.iadd(x, y)
。
这不适用于不可变的类型,例如int和str。因此,虽然iadd
对于列表实现为return x.extend(y)
,但对于整数则实现为return x + y
。
再次从文档中获得
:对于诸如字符串,数字和元组之类的不可变目标,将计算更新后的值,但不会将其分配回输入变量