翻譯自《Python Virtual Machine》python
Python 虛擬機
每一個函數對象都和如下的三個結構:
1。包含參數的局部變量名稱(in .__code__.varnames)
2。全局變量名稱(in .__code__.co_names)
3。常數(in .__code__.co_consts)
在python定義函數的時候建立這些結構,它們被定義在函數對應的__code__對象。
若是咱們定義以下:
Def minimum(alist):
m=None if let(alist) ==0 else Alist[0]
for v in alist[1:]:
if vim:
m = v
return m
咱們獲得
minimum.__code__.co_varnames is ('alist','m','v')
minimum.__code__.co_names is ('len','None')
minimum.__code__.co_consts is (None,0,1)
用於索引的數字+load 運算符(LOAD_FAST、LOAD_GLOBAL、LOAD_CONST都會在以後討論)。
在PVM中主要的數據結構式「regular」棧(由一連串的push、pop組成)。對棧的主要操做就是load/push和store/pop。咱們在棧頂load/push一個值,棧向上擴展,伴隨着棧指針上移指向棧頂。一樣,store/pop一個棧頂值時,棧指針下移。
還有一個次要的block棧用於從循環嵌套、try和指令中取出值。好比,一個斷點指令在block棧中被編碼,用於判斷哪一個循環塊被n斷下(並如何繼續執行循環外的指令)。當循環,try/except和指令開始執行時,他們的信息被push到block棧上;當它們結束時從堆棧上彈出。這種塊block stack對於如今來講太過麻煩,沒必要要去理解:因此當咱們遇到有關block stack 的指令時,會指出將其忽略的緣由。
這兒有個有關棧操做的簡單例子,計算 d=a+b*c。假設a、b、c、d都是一個函數中的局部變量:co_varnames =('a','b','c','d')且這些符號對應的實際值被存放在並行元組中:(1,2,3,none)。符號在元組中的位置與其值在元組的位置是一一對應的。
LOAD_FAST N
load/push 將co_varnames[N]對應的值壓入棧,stackp+=1,stack[stackp] = co_varnames[N]
STORE_FAST N
store/pop 將棧頂的值放入co_varnames[N], co_varnames[N] = stack[stackp], stackp-=1
BINARY_MULTIPLY
將‘*’的兩個運算數壓入棧,stack[stackp-1]=stack[stackp-1]*stack[stack];stackp-=1(將棧頂的兩個值轉化爲它們的乘積)
BINARY_ADD
將‘+’的兩個運算數壓入棧,stack[stackp-1]=stack[stackp-1]+stack[stack];stackp-=1(將棧頂的兩個值轉化爲它們的和)
d = a+b*c 的PVM code:
LOAD_FAST 0
LOAD_FAST 1
LOAD_FAST 2
BINARY_MULTIPLY
BINARY_ADD
STORE_FAST 3
初始狀態:
co_varnames =('a','b','c','d')
values=(1,2,3,none)
+--------------------+
3 | |
+--------------------+
2 | |
+--------------------+
1 | |
+--------------------+
0 | |
+--------------------+
stack (with stackp=-1,it is an empty stack)
LOAD_FAST 0:
+--------------------+
3 | |
+--------------------+
2 | |
+--------------------+
1 | |
+--------------------+
0 | 1: value of a |
+--------------------+
stack(with stackp=0)
LOAD_FAST 1:
+--------------------+
3 | |
+--------------------+
2 | |
+--------------------+
1 | 2: value of b |
+--------------------+
0 | 1: value of a |
+--------------------+
stack (with stackp=1)
LOAD_FAST 2:
+--------------------+
3 | |
+--------------------+
2 | 3: value of c |
+--------------------+
1 | 2: value of b |
+--------------------+
0 | 1: value of a |
+--------------------+
stack (with stackp=2)
BINARY_MULTIPLY:
+--------------------+
3 | |
+--------------------+
2 | |
+--------------------+
1 | 6: value of b*c |
+--------------------+
0 | 1: value of a |
+--------------------+
stack (with stackp=1)
BINARY_ADD:
+--------------------+
3 | |
+--------------------+
2 | |
+--------------------+
1 | |
+--------------------+
0 | 7: value of a+b*c |
+--------------------+
stack (with stackp=0)
STORE_FAST 3:
+--------------------+
3 | |
+--------------------+
2 | |
+--------------------+
1 | |
+--------------------+
0 | |
+--------------------+
stack (with stackp=-1)
co_varnames =('a','b','c','d')
values=(1,2,3,7)
PVM的控制流
在PVM的每一個指令都包含了1~3字節的信息。第一個字節是操做標識或字節碼,後面的兩字節是字節碼的操做數(但並非全部的字節碼都須要操做數:BINARY_ADD就不須要)。兩字節可以表示0~65536:因此python的函數中不能有超過65536個不一樣的局部變量。
指令被儲存在內存中:把內存也看做一種儲存有次序的數據的列表結構。
Memory Instruction
Location
0 LOAD_FAST 0
3 LOAD_FAST 1
6 LOAD_FAST 2
9 BINARY_MULTIPLY
10 BINARY_ADD
11 STORE_FAST 3
把內存列表命名爲m
第一條指令被存儲在m[0],後一指令存儲在高3或高1的位置處(佔3字節:有些指令有明確操做數的:load/store。有些指令有隱含的操做數:stack 、pc。佔1字節:沒有操做數的指令:binary運算)
一旦這些指令被加載進內存後,PVM按照一個簡單的規則執行他們。執行週期賦予了計算機生命,這是計算機科學的基礎。
(1)從m [pc]開始獲取操做及其操做數(若是存在)
(2)pc + = 3(若是操做數存在)或pc + = 1(若是沒有操做數存在)
(3)執行操做碼(可能更改其操做數,堆棧,堆棧或pc)
(4)轉到步驟1
一些運算會操做stack/stackp和存變量值的元組,一些會改變pc(好比jump指令)。
因此pc初始時0,PVM執行上述代碼以如下流程:
1.獲取操做m [0],操做數m [1]和m [2]
2.將pc遞增至3
3.操縱堆棧(見上文)
4.回到步驟1
1.取m [3]的操做,m [4]和m [5]
2.將pc增長到6
3.操縱堆棧(見上文)
4.回到步驟1
1.取m [6]和m [7]和m [8]的操做數,
2.將pc增長到9
3.操縱堆棧(見上文)
4.回到步驟1
1.獲取操做a m [9]:它沒有操做數
2.將pc增長到10
3.操縱堆棧(見上文)
4.回到步驟1
1.獲取操做m [10]:它沒有操做數
2.將pc增長到11
3.操縱堆棧(見上文)
4.回到步驟1
內存中指向此處時,沒有代碼能夠執行。在下一個例子中咱們能夠看到PVM如何執行一個更復雜的代碼。
如簡要介紹的那樣,咱們能夠用dis.py模塊中使用dis函數打印任何Python函數(和模塊/類也能夠)的註釋描述;這裏咱們打印函數。
def addup(alist):
sum=0
for v in alist:
sum = sum + v
return sum
這個例子用來顯示通常函數對象的有用的信息(它的名稱,它的三個元組,和反編譯信息)
def func_obj(fo):
print(fo.__name__)
print(' co_varnames:',fo.__code__.co_varnames)
print(' co_names :',fo.__code__.co_names)
print(' co_consts :',fo.__code__.co_consts,'\n')
print('Source Line m operation/byte-code operand (useful name/number)\n'+69*'-')
dis.dis(fo)
calling func_obj(addup) prints
addup
co_varnames: ('alist', 'sum', 'v')
co_names : ()
co_consts : (None, 0)
Source Line m op/byte-code operand (useful name/number)
---------------------------------------------------------------------
2 0 LOAD_CONST 1 (0)
3 STORE_FAST 1 (sum)
3 6 SETUP_LOOP 24 (to 33)
9 LOAD_FAST 0 (alist)
12 GET_ITER
>> 13 FOR_ITER 16 (to 32)
16 STORE_FAST 2 (v)
4 19 LOAD_FAST 1 (sum)
22 LOAD_FAST 2 (v)
25 BINARY_ADD
26 STORE_FAST 1 (sum)
29 JUMP_ABSOLUTE 13
>> 32 POP_BLOCK
5 >> 33 LOAD_FAST 1 (sum)
36 RETURN_VALUE
有>>標識的行說明有其餘指令會jump到此行。
更詳細的描述:
第2行:
m [0]:在堆棧上加載值0(co_consts [1])
m [3]:將值0存入sum(co_varnames [1])
第3行:
m [6]:經過將循環塊的大小壓入棧來設置循環
m [9]:從棧中加載alist(co_varnames [0])的值
m [12]:經過迭代器替換堆棧上的值(經過彈出和推送)
m [13]:在堆棧中加載下一個迭代器值,若是StopIteration引發,則跳轉到m [32]
(m [29]中的代碼跳回此位置進行循環)
m [16]:將下一個值存儲到v(co_varnames [2])中,將其從堆棧中彈出
第4行:
m [19]:在棧中加載sum(co_varnames [1])的值
m [22]:將v(co_varnames [2])的值加載到棧上
m [25]:將棧頂的兩個值進行相加操做後,將結果加載到棧上
m [26]:棧頂彈出值,存儲在sum(co_varnames [1])中
m [29]:將pc設置爲13,所以在m [13]中執行的下一條指令
(跳回到前一個位置使循環循環)
m [32]:彈出m[6]對循環塊的設置而壓入棧的值
(m [13]中的代碼在這裏跳轉到StopIteration,終止循環)
第5行:
m [33]:將sum(co_varnames [1])的值壓入棧,用於返回
m [36]:從函數返回結果在堆棧頂部