在我們使用Python
的過程中, 經常遇到賦值語句, 就像下面的那樣:
1 2 |
a = 3 b = 3 |
可能你會覺得我又要說什麼變數賦值就是引用, 這麼簡單的知識就不討論啦, 相信聰明的大家肯定都知道的, 我想講的是鏈式賦值
先科普下什麼是鏈式賦值
:
1 |
鏈式賦值: 同時對幾個變數進行賦值 |
例如:
1 |
a = b = c = 3 |
好了, 現在正式進入正題:
1 2 3 |
>>> s = [1, 2, 3, 4, 5, 6] >>> i = 0 >>> i = s[i] = 3 |
i 和 s 的值分別是什麼? 可能大家一眼看下去, 就能得出答案:
1 2 |
i 的值: 3 s 的值: [3, 2, 3, 4, 5, 6] |
然而, 這個答案只是對了一半, 因為s的值錯了! 有興趣的朋友可以自行上機試下, 正確答案是:
1 2 |
i 的值: 3 s 的值: [1, 2, 3, 3, 5, 6] |
s的列表, 並沒有像我們想象中的那樣, 就i=0
位置上的元素, 變成3, 而是將i=3
位置的元素改成3了, 為什麼會這樣? 一起來解析下吧, 上dis
大殺器!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
[root@iZ23pynfq19Z ~]# cat 2.py s = [1, 2, 3, 4, 5] i = 0 g = i = s[i] = 3 [root@iZ23pynfq19Z ~]# python -m dis 2.py 1 0 LOAD_CONST 0 (1) 3 LOAD_CONST 1 (2) 6 LOAD_CONST 2 (3) 9 LOAD_CONST 3 (4) 12 LOAD_CONST 4 (5) 15 BUILD_LIST 5 18 STORE_NAME 0 (s) 2 21 LOAD_CONST 5 (0) 24 STORE_NAME 1 (i) 3 27 LOAD_CONST 2 (3) 30 DUP_TOP 31 STORE_NAME 2 (g) 34 DUP_TOP 35 STORE_NAME 1 (i) 38 LOAD_NAME 0 (s) 41 LOAD_NAME 1 (i) 44 STORE_SUBSCR 45 LOAD_CONST 6 (None) 48 RETURN_VALUE |
第一列的數字, 代表中間的位元組碼
是屬於哪一行程式碼的.
第1~2行簡單解釋下:
分別LOAD_CONST
5個數字, 組成一個列表, 賦值給s,再取一個0, 賦值給i.接下來的就是我們關心的, 也是帶給我們意外的程式碼.
第3行:
LOAD_CONST
取出常量3, 它並不是像上面執行STORE_NAME
, 而是採用DUP_TOP
, 這是什麼鬼, 我們這要去看下這指令具體是幹嘛的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//取自 python/ceval.c PyEval_EvalFrameEx(PyFrameObject *f, int throwflag) { ... (省略) TARGET_NOARG(DUP_TOP) { v = TOP(); // 複製執行棧幀的頂部值 Py_INCREF(v); // 增加引用計數 PUSH(v); // 再壓入執行棧幀 FAST_DISPATCH(); } ... (省略) } |
DUP_TOP
指令說白了, 就是將剛才LOAD_CONST
指令取出的常量3, 複製一份給v,然後再壓回去執行棧幀, 這樣就有兩個3了, 為什麼要這麼做, 肯呢個大家已經猜到了, 不過我們還是得繼續看具體是不是像我們想的那樣, 繼續看會位元組碼:
1 2 3 4 |
35 STORE_NAME 1 (i) 38 LOAD_NAME 0 (s) 41 LOAD_NAME 1 (i) 44 STORE_SUBSCR |
果然不出我們所料, 開始將這些3通過STORE_NAME
賦值給i, 而對於s, 它反而是, 再一次LOAD_NAME
取出i的值, 此時i的值是3, 不是一開始的0了, 在通過STORE_SUBSCR
指令, 將剛才壓入執行時時棧的3賦值給位置是3的元素, 具體的原始碼就不再看, 到這就夠了.
所以看到這, 相信大家都能清楚, 為什麼結果是 [1, 2, 3, 3, 5, 6]
這跟我們想象中的鏈式賦值很不同, 我們以前總是覺得, 賦值要從右到左依次執行, 先執行 s[i] = 3
, 再執行 i=3
, 然而這些是類似c語言
這類支援表示式賦值才允許的. 在c語言
中, s = 3
表示式是有返回值的. 它會返回賦值的結果3, 所以在它們的鏈式賦值
中, 是將右邊表示式的返回值, 再賦值給左邊的, 例如:
1 2 3 4 |
a = s = 3 等價於: a = (s = 3) 也就是 s=3 返回3, 再賦值給a |
而在python
是不支援這種表示式賦值的, 也就是表示式是沒有返回值的, 如果硬要a = (s = 3)
只會觸發SyntaxError: invalid syntax
希望大家以後在用到這種鏈式賦值時, 儘量避免這些問題哦
感謝@Daetalus 童鞋指出問題:
支援表示式賦值是Python語言的核心,比如a = b + 3。這裡的 b + 3
就是表示式。如果不支援表示式賦值就沒法繼續下去了。a = (s = 3)出錯的原因是因為s = 3是賦值語句,而不是表示式。
Python的表示式是由操作符連線而成的,但“=”在Python中並不是操作符(Operator),只是語法分隔符(Delimiters)。參見:https://docs.python.org/release/3.6.1/reference/lexical_analysis.html#operators