流畅的Python书籍,示例2.15
我正在读Luciano Ramalho的书fluent Python,他写道,如果我们编写这样的代码:流畅的Python书籍,示例2.15,python,Python,我正在读Luciano Ramalho的书fluent Python,他写道,如果我们编写这样的代码: >>>t=(1,2,[30,40]) >>>t[2]+=[50,60] t = (1,2,3) t2 = (4,5,6) t3 = t + t2 ,然后我们将得到: a。t变成(1,2,[30,40,50,60])。 BTypeError是用 消息“tuple”对象不支持项分配 我完全明白这一点 但他的一位读者后来说,如果我们编写如下代码: >&g
>>>t=(1,2,[30,40])
>>>t[2]+=[50,60]
t = (1,2,3)
t2 = (4,5,6)
t3 = t + t2
,然后我们将得到:
a。t变成(1,2,[30,40,50,60])。BTypeError是用 消息“tuple”对象不支持项分配 我完全明白这一点 但他的一位读者后来说,如果我们编写如下代码:
>>>t=(1,2,[30,40])
>>>t[2].extend([50,60])
,此时不会引发TypeError
消息。我想知道为什么会这样
如果可能的话,试着用图片来描述代码的执行情况。
谢谢你
t=(1,2,[30,40])
t2 += [50,60]
这将不起作用,因为t2引用的变量不存在
t=(1,2,[30,40])
t += [50,60]
这也将不起作用,因为您不能用列表扩展元组,您将得到如下错误TypeError:只能将元组(而不是“列表”)连接到元组
t=(1,2,[30,40])
t[2] += [50,60]
这是因为您正在扩展元组中的第三个元素列表。如果然后打印元组,它将如下所示:(1,2,30,40,50,60])
请注意,元组通常是不可变的,这意味着您不能在创建元组后追加值。但是,像这样的事情:
>>>t=(1,2,[30,40])
>>>t[2]+=[50,60]
t = (1,2,3)
t2 = (4,5,6)
t3 = t + t2
有效,因为您通过添加前两个元组创建了一个新元组,但没有更改它们。我希望这能澄清一点
此外,在某些情况下,如果它们包含可变的对象,那么它们也被认为是可变的(如果我错了,请纠正我!)。从技术上讲,我可能不是100%正确,但我将尝试尽可能直观地解释。 第一种情况:
>>>t=(1,2,[30,40])
>>>t[2]+=[50,60]
让我们看看发生了什么:
当您访问t[2]
时,它会选择列表[30,40]
,现在在执行+=
操作时,它会以特定方式将[50,60]
添加到t[2]
:
t[2]=t[2]。\uuuuu-iadd([50,60])
现在右侧是有效的,这就是为什么t[2]
指向的列表在操作后发生了更改,但赋值部分是问题所在,tuple不支持项赋值
第二种情况:
>>>t=(1,2,[30,40])
>>>t[2].extend([50,60])
这里没有涉及副本,因此不需要将副本分配回t[2]
您只是扩展了t[2]
所引用的列表
让我们看一些不同的例子:
>>> x = [30,40]
>>> t1 = (1,2,x)
>>> t1
(1, 2, [30, 40])
>>> x += [50,60]
>>> x
[30, 40, 50, 60]
>>> t1
(1, 2, [30, 40, 50, 60])
在这里,x
指向一个列表[30,40]
。而t1
包含该引用。现在您可以独立地修改x
,因为您只是在修改列表,现在x
引用的列表已经变成[30,40,50,60]
,因为t1[2]
包含了对同一列表的引用,它现在显示[30,40,50,60]
,所以一点也不奇怪
另一个例子:
>>> t1 = (1, 2, [30, 40])
>>> x = t1[2]
>>> x += [50,60]
>>> x
[30, 40, 50, 60]
>>> t1
(1, 2, [30, 40, 50, 60])
这里,t1[2]
指的是一个列表[30,40]
,您决定给同一个列表起另一个名字(x
)。现在您修改了所引用的列表,x
,对此没有任何限制,list
s是可变对象,因此不会出现任何错误,t[2]
指向同一个列表时,您不会试图在t[2]
中存储另一个修改后的列表,只是t[2]
指向的列表,它本身已经改变了
最后,如果我们看一下字节码反汇编,就会更清楚:
>>> import dis
>>> def f1():
... t = (1,2,[30,40])
... t[2]+=[50,60]
... return t
>>> def f2():
... t = (1,2,[30,40])
... t[2].extend([50,60])
... return t
>>> dis.dis(f1)
2 0 LOAD_CONST 1 (1)
2 LOAD_CONST 2 (2)
4 LOAD_CONST 3 (30)
6 LOAD_CONST 4 (40)
8 BUILD_LIST 2
10 BUILD_TUPLE 3
12 STORE_FAST 0 (t)
3 14 LOAD_FAST 0 (t)
16 LOAD_CONST 2 (2)
18 DUP_TOP_TWO
20 BINARY_SUBSCR
22 LOAD_CONST 5 (50)
24 LOAD_CONST 6 (60)
26 BUILD_LIST 2
28 INPLACE_ADD # t[2].__iadd__([50,60])
30 ROT_THREE
32 STORE_SUBSCR # tries to store; t[2] = t[2].__iadd__([50,60])
4 34 LOAD_FAST 0 (t)
36 RETURN_VALUE
>>> dis.dis(f2)
2 0 LOAD_CONST 1 (1)
2 LOAD_CONST 2 (2)
4 LOAD_CONST 3 (30)
6 LOAD_CONST 4 (40)
8 BUILD_LIST 2
10 BUILD_TUPLE 3
12 STORE_FAST 0 (t)
3 14 LOAD_FAST 0 (t)
16 LOAD_CONST 2 (2)
18 BINARY_SUBSCR
20 LOAD_METHOD 0 (extend) # loads the same list in t[2]
22 LOAD_CONST 5 (50)
24 LOAD_CONST 6 (60)
26 BUILD_LIST 2
28 CALL_METHOD 1
30 POP_TOP # no store calls
4 32 LOAD_FAST 0 (t)
34 RETURN_VALUE
在您的第一行,t2
不是t
,因此生成您提到的错误的代码似乎不完整。Nvm。我现在明白了--t=(1,2,[30,40])
然后t[2]+=[50,60]
“现在在执行+=操作时,它首先创建列表的副本”我很确定这不是真的。如果您将t
转换为list(只允许就地添加)并保留一个引用t2=t[2]
,然后执行t[2]+=无论什么,您都会发现t2
被修改了,而不是副本。是的,t[2]
被修改是t[2]
变为[30,40,50,60]的原因
之后,即使它引发了TypeError
,但它也试图在t[2]
(32 store\u SUBSCR
)中存储一个列表[30,40,50,60]
),从而导致问题。不是列表,而是同一个列表。除[50,60]
外,未创建新列表。返回赋值之所以存在,是因为不能保证inplace add在原地。例如,如果t[2]
不是一个列表而是一个数字,那么t[2]+=其他一些数字将创建一个新对象。