流畅的Python书籍,示例2.15

流畅的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

我正在读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”对象不支持项分配

我完全明白这一点

但他的一位读者后来说,如果我们编写如下代码:

>>>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]+=其他一些数字将创建一个新对象。