python虛擬機


翻譯自《Python Virtual Machine》

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]:從函數返回結果在堆棧頂部


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM