python虛擬機

翻譯自《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]:從函數返回結果在堆棧頂部
相關文章
相關標籤/搜索