原文鏈接: 一個關于 += 的謎題
今天在看書程序中發現了一個問題,還挺有意思的,分享給大家,
下面兩個 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、資料結構、演算法、編程、網路等各種常考題,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/434380.html
標籤:Python
