乙個關於 += 的謎題
今天在看書過程中發現了乙個問題,還挺有意思的,分享給大家。
下面兩個 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
>>>
>>> t = (1, 2, [3, 4])
>>> t[2] += [5, 6]
traceback (most recent call last):
file "", line 1, in typeerror: 'tuple' object does not support item assignment
結果是沒問題的,t
被修改了,但是也報錯了。
還可以在 python tutor 上分析一下:
這個**可以視覺化分析 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、資料結構、演算法、程式設計、網路等各種常考題。
類謎題 關於靜態域的乙個小問題
知識點 靜態與只有乙個,沒有拷貝,是共享的。第二個 關於使用整合還是組合。下面的程式使用了乙個counter類來跟蹤每一種家庭寵物叫喚的次數。那麼該程式會列印出什麼呢?class counter public static final synchronized int getcount class ...
關於玩具謎題的問題
原題 有 n個玩具小人圍成一圈,已知它們的職業和朝向。現在第1個玩具小人告訴小南乙個包含m條指令的謎題,其中第 z條指令形如 左數 右數第s,個玩具小人 你需要輸出依次數完這些指令後,到達的玩具小人的職業。輸入的第一行包含兩個正整數 n,m,表示玩具小人的個數和指令的條數。接下來 n行,每行包含乙個...
關於乙個加法優化的乙個地方
include include include base.h int main int argc,char argv,char envp 下面是彙編 01291000 55 push ebp 01291001 8bec mov ebp,esp 01291003 56 push esi 0129100...