元組可以被看做為不可變列表稽物,也就是說元組的里的元素是不能隨意改變的奄毡。但是,元組是可以接受一個可變對象——列表作為元素的贝或。這會產(chǎn)生一些意想不到的邊界效果吼过。摘自《流暢的 Python》第 2.6 章節(jié)。
1 一個謎題
定義一個接受可變對象——列表作為元素的元組傀缩,然后利用切片操作列表對象那先。
>>> t = (1, 2 ,[ 30, 40])
>>> t[2] += [50, 60]
到底會發(fā)生下面 4 種情況的哪一種?
A. t 變成 (1赡艰, 2 售淡, [30, 40, 50, 60])。
B. 因為 tuple 不支持對它的元素賦值慷垮,所以會拋出 TypeError 異常揖闸。
C. 以上兩個都不是。
D. A 和 B 都對料身。
2 正確答案
D汤纸。t[2] 被改動了,但是也有異常拋出芹血。
>>> t[2] += [50, 60]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> t
(1, 2, [30, 40, 50, 60])
3 解惑
下面來看看 Python 為表達式 t[2] += [50, 60] 生成的字節(jié)碼贮泞,可能這個現(xiàn)象背后的原因會變得清晰起來。
>>> import dis
>>> dis.dis('t[2] += [50, 60]')
1 0 LOAD_NAME 0 (t)
2 LOAD_CONST 0 (2)
4 DUP_TOP_TWO
6 BINARY_SUBSCR ①
8 LOAD_CONST 1 (50)
10 LOAD_CONST 2 (60)
12 BUILD_LIST 2
14 INPLACE_ADD ②
16 ROT_THREE
18 STORE_SUBSCR ③
20 LOAD_CONST 3 (None)
22 RETURN_VALUE
① 將 t[2] 的值存入 TOS (Top Of Stack幔烛,棧的頂端)啃擦。
② 計算 TOS += b。這一步能夠完成饿悬, 是因為 TOS 指向的是一個可變對象(也就是實例中的列表)令蛉。
③ t[2] = TOS 賦值。這一步失敗狡恬,是因為 t 是不可變的元組珠叔。
4 教訓(xùn)
- 不要把可變對象放在元組里。
- 增量賦值不是一個原子操作弟劲。我們剛才也看到了祷安,它雖然拋出了異常,但還是完成了操作兔乞。
- 產(chǎn)看 Python 的字節(jié)碼并不難汇鞭,而且它對我們了解代碼背后的運行機制很有幫助撇眯。
5 選修題
** 如果寫成 t[2].extend([50, 60])
就能避免這個問題,為什么虱咧?**