你可曾想過這段 Python 程式碼是如何運作的?
1 2 3 4 5 |
>>> a = 5 >>> b = 10 >>> c = a * b >>> c 50 |
Python 作為 interpreted language,其運作可以分為兩個大項,Compiler 以及 Virtual Machine。Compiler 負責將輸入的語法做分析,轉換成 AST (Abstract Syntax Tree),再轉換成 CFG,最後依照 CFG 輸出 bytecode,Code Object 等必要的物件。Virtual Machine 則根據 bytecode 來運行,最後輸出程式運行的結果。
我們可以透過 Python 的 dis
模組,來得知程式碼的 Byte Code 是什麼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
➜ cpython git:(master) ✗ cat tests.py a = 5 b = 7 c = a + b ➜ cpython git:(master) ✗ ./python –m dis tests.py 1 0 LOAD_CONST 0 (5) 2 STORE_NAME 0 (a) 2 4 LOAD_CONST 1 (7) 6 STORE_NAME 1 (b) 3 8 LOAD_NAME 0 (a) 10 LOAD_NAME 1 (b) 12 BINARY_ADD 14 STORE_NAME 2 (c) 16 LOAD_CONST 2 (None) 18 RETURN_VALUE |
詳細的 Byte Code 資訊可以到 32.12 dis – Disassembler for Python bytecode
第一行 a = 5
對應的是 LOAD_CONST
以及 STORE_NAME
這兩個 bytecode,透過 LOAD_CONST
將 const 放置到 stack top,接著 STORE_NAME
把 stack top 的 value 存到 a
這個變數名稱。第二行也是相同,第三行比較長,兩個 LOAD_NAME
之後使用 BINARY_ADD
將 stack top 以及 second 的數值相加後放回 stack top,使用 STORE_NAME
存到 c 變數中。最後的 LOAD_CONST (None)
以及 RETURN_VALUE
是附加上去的,跟三行程式碼沒有關係。
從這邊可以看出為什麼 Python 被稱為 dynamic type 的語言,在 VM 運行 bytecode 之前,Compiler 對型態是不會做檢查的。LOAD_CONST
並不只能用於 integer,string 也通用,BINARY_ADD
也是,不論前面 LOAD_NAME
的變數型態為何,BINARY_ADD
都可以執行。型態檢查只會在 bytecode 的實作中執行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
TARGET(BINARY_ADD) { PyObject *right = POP(); PyObject *left = TOP(); PyObject *sum; // 檢查是否為字串,如果是,執行 unicode_concatenate if (PyUnicode_CheckExact(left) && PyUnicode_CheckExact(right)) { sum = unicode_concatenate(left, right, f, next_instr); /* unicode_concatenate consumed the ref to left */ } // 如果不是,執行 PyNumber_Add else { sum = PyNumber_Add(left, right); Py_DECREF(left); } Py_DECREF(right); SET_TOP(sum); if (sum == NULL) goto error; DISPATCH(); } |
前面提到 STORE_NAME
的時候用了「存」這個字,我覺得並沒有很精確,應該用 「bind」或是「set」來指涉。簡單來說,你可以想成 locals_variables[name] = stack_top()
,因為牽扯到 frame,這邊不細談。
理論上,你可以用 Python 簡單的寫一個 VM 出來執行上面的 bytecode,也確實有人寫出來,可以參考 nedbat/byterun 以及 Allison Kaptur – Bytes in the Machine。
總結而言,CPython 實作了一個 Python Virtual Machine,透過這個 VM 解讀bytecode,變能夠運行程式。至於 CPython 如何實作,可以參考 Python/ceval.c,簡單來說,就是一個 for(;;) 加上超級大的 switch。
Leave a Reply