一個關于 += 的謎題
原文鏈接: 一個關于 += 的謎題
今天在看書過程中發現了一個問題,還挺有意思的,分享給大家。
下面兩個 Python 表達式會產生什么結果?
t = (1, 2, [3, 4])
t[2] += [5, 6]
給四個備選答案:
t變成(1, 2, [3, 4, 5, 6])。- 因為 tuple 不支持對它的元素賦值,所以會拋出
TypeError異常。 - 以上兩個都不是。
- 以上兩個都是對的。
當時看到這個問題,第一反應就是選 2。因為 tuple 是不可變對象,不支持對它的元素賦值,會報錯。
但事實上,這道題的正解是 4。
在終端里驗證一下:
Python 3.8.2 (default, Oct 2 2020, 10:45:42)
[Clang 12.0.0 (clang-1200.0.32.27)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
>>> t = (1, 2, [3, 4])
>>> t[2] += [5, 6]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
結果是沒問題的,t 被修改了,但是也報錯了。
還可以在 Python Tutor 上分析一下:
網站地址: https://pythontutor.com/
這個網站可以可視化分析 Python 的運行過程和原理。
執行第一個表達式:

執行第二個表達式:

為什么會這樣呢?可以從兩個方面來解釋:
一、對象類型
Python 中的對象可以分成兩類,可變對象和不可變對象,比如一些內置類型:
- 可變對象:list,set,dict。
- 不可變對象:int,float,bool,string,tuple。
舉一個例子:
可變對象:
>>> a = [1, 2, 3]
>>> id(a)
2139167246856
>>> b = a
>>> id(b)
2139167246856
>>> a[1] = 4
>>> a
[1, 4, 3]
>>> b
[1, 4, 3]
>>> id(a)
2139167246856
>>> id(b)
2139167246856
可以看到,改變 a 的同時 b 也跟著變,因為他們始終指向同一個地址。
不可變對象:
>>> a = (1, 2, 3)
>>> id(a)
2139167074776
>>> b = a
>>> a = (4, 5, 6)
>>> a
(4, 5, 6)
>>> b
(1, 2, 3)
>>> id(a)
2139167075928
>>> id(b)
2139167074776
可以看到,a 的值改變后,它的地址也發生了變化,而 b 還是原來的地址,并且原地址中的內容也沒有發生變化。
二、字節碼
首先解釋一下字節碼是什么?
Python 執行程序時會把源碼文件編譯成字節碼文件,存放在 __pycahe 目錄內,文件用 .pyc 結尾。之后如果不再修改源碼文件,運行時則直接使用 .pyc 文件編譯成機器碼,這樣不但運行速度快,而且支持多個操作系統。
字節碼,其實就是一種中間代碼。
下面用 dis 模塊來看一下表達式 s[a] += b 的執行過程:
>>> import dis
>>> dis.dis('s[a] += b')
1 0 LOAD_NAME 0 (s)
2 LOAD_NAME 1 (a)
4 DUP_TOP_TWO
6 BINARY_SUBSCR
8 LOAD_NAME 2 (b)
10 INPLACE_ADD
12 ROT_THREE
14 STORE_SUBSCR
16 LOAD_CONST 0 (None)
18 RETURN_VALUE
>>>
通過分析字節碼,可以看到其中的關鍵三步:
4 DUP_TOP_TWO:將s[a]存入 TOS(Top Of Stack)。10 INPLACE_ADD:執行TOS += b,帶入到文章開頭的表達式,就相當于向t[2]中添加元素,因為t[2]是 list,可變對象,所以這一操作沒有問題。14 STORE_SUBSCR:將結果保存回s[a] = TOS,這相當于將結果重新賦值回t,由于t是 tuple,不可變對象,所以報錯。
雖然這個問題在平時開發中可能并不常見,但通過分析還是有不少知識點可以深挖的。
簡單總結以下三點:
- 不要把可變對象放在元組里面。
- 增量賦值不是一個原子操作。我們剛才也看到了,它雖然拋出了異常,但還是完成了操作。
- 查看 Python 的字節碼并不難,而且它對我們了解代碼背后的運行機制很有幫助。
以上就是本文的全部內容,如果覺得還不錯的話,歡迎點贊和轉發,多謝
推薦閱讀:
- 計算機經典書籍(含下載方式)
- 技術博客: 硬核后端開發技術干貨,內容包括 Python、Django、Docker、Go、Redis、ElasticSearch、Kafka、Linux 等。
- Go 程序員: Go 學習路線圖,包括基礎專欄,進階專欄,源碼閱讀,實戰開發,面試刷題,必讀書單等一系列資源。
- 面試題匯總: 包括 Python、Go、Redis、MySQL、Kafka、數據結構、算法、編程、網絡等各種常考題。

浙公網安備 33010602011771號