首頁  >  文章  >  後端開發  >  Python虛擬機字節碼之裝飾器怎麼實現

Python虛擬機字節碼之裝飾器怎麼實現

WBOY
WBOY轉載
2023-05-04 08:31:06858瀏覽

Python 常見字節碼

LOAD_CONST

這個指令用來將一個常數載入到堆疊中。常數可以是數字、字串、元組、列表、字典等物件。例如:

>>> dis.dis(lambda: 42)
  1           0 LOAD_CONST               1 (42)
              2 RETURN_VALUE

LOAD_NAME

這個指令用來將一個變數載入到堆疊中。例如:

>>> dis.dis(lambda: x)
  1           0 LOAD_GLOBAL              0 (x)
              2 RETURN_VALUE
>>>

STORE_NAME

這個指令用來將堆疊頂端的值儲存到一個變數中。例如:

>>> dis.dis("x=42")
  1           0 LOAD_CONST               0 (42)
              2 STORE_NAME               0 (x)
              4 LOAD_CONST               1 (None)
              6 RETURN_VALUE

BINARY_ADD

這個指令用來對堆疊頂端的兩個值進行加法運算並將結果推送到堆疊中。

>>> dis.dis(lambda: x + y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_ADD
              6 RETURN_VALUE

BINARY_SUBTRACT

這個指令用來對堆疊頂端的兩個值進行減法運算並將結果推送到堆疊中。

>>> dis.dis(lambda: x - y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_SUBTRACT
              6 RETURN_VALUE

同樣的加減乘除取餘數的字節碼如下所示:

>>> dis.dis(lambda: x + y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_ADD
              6 RETURN_VALUE
>>> dis.dis(lambda: x - y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_SUBTRACT
              6 RETURN_VALUE
>>> dis.dis(lambda: x * y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_MULTIPLY
              6 RETURN_VALUE
>>> dis.dis(lambda: x / y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_TRUE_DIVIDE
              6 RETURN_VALUE
>>> dis.dis(lambda: x // y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_FLOOR_DIVIDE
              6 RETURN_VALUE
>>> dis.dis(lambda: x % y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_MODULO
              6 RETURN_VALUE

COMPARE_OP

這個指令用來比較堆疊頂的兩個值,並且將比較得到的結果壓入堆疊中,這個字節碼後面後一個位元組的參數,表示小於大於不等於等等比較符號。例如:

>>> dis.dis(lambda: x - y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 BINARY_SUBTRACT
              6 RETURN_VALUE
>>> dis.dis(lambda: x > y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 COMPARE_OP               4 (>)
              6 RETURN_VALUE
>>> dis.dis(lambda: x < y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 COMPARE_OP               0 (<)
              6 RETURN_VALUE
>>> dis.dis(lambda: x != y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 COMPARE_OP               3 (!=)
              6 RETURN_VALUE
>>> dis.dis(lambda: x <= y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 COMPARE_OP               1 (<=)
              6 RETURN_VALUE
>>> dis.dis(lambda: x >= y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 COMPARE_OP               5 (>=)
              6 RETURN_VALUE
>>> dis.dis(lambda: x == y)
  1           0 LOAD_GLOBAL              0 (x)
              2 LOAD_GLOBAL              1 (y)
              4 COMPARE_OP               2 (==)
              6 RETURN_VALUE

RETURN_VALUE

將堆疊頂元素彈出作為傳回值。

BUILD_LIST

這個指令用來建立一個清單。例如:

>>> dis.dis(lambda: [a, b, c, e])
  1           0 LOAD_GLOBAL              0 (a)
              2 LOAD_GLOBAL              1 (b)
              4 LOAD_GLOBAL              2 (c)
              6 LOAD_GLOBAL              3 (e)
              8 BUILD_LIST               4
             10 RETURN_VALUE

這條字節碼指令有一個參數表示堆疊空間當中列表元素的個數,在上面的例子當中這個參數是 4 。

BUILD_TUPLE

這個指令用來建立一個元組。例如:

>>> dis.dis(lambda: (a, b, c))
  1           0 LOAD_GLOBAL              0 (a)
              2 LOAD_GLOBAL              1 (b)
              4 LOAD_GLOBAL              2 (c)
              6 BUILD_TUPLE              3
              8 RETURN_VALUE

同樣的這個字節碼也有一個參數,表示建立元組的元素個數。

BUILD_MAP

這個指令用來建立一個字典。例如:

BUILD_SET

和list 和tuple 一樣,這條指令是用來建立一個集合對象,同樣的這條指令也有一個參數表示用來建立集合的元素的個數。

>>> dis.dis(lambda: {a, b, c, d})
  1           0 LOAD_GLOBAL              0 (a)
              2 LOAD_GLOBAL              1 (b)
              4 LOAD_GLOBAL              2 (c)
              6 LOAD_GLOBAL              3 (d)
              8 BUILD_SET                4
             10 RETURN_VALUE

BUILD_CONST_KEY_MAP

這條指令是用來建立一個字典對象,同樣的這條指令也有一個參數,表示字典當中元素的個數。

>>> dis.dis(lambda: {1:2, 3:4})
  1           0 LOAD_CONST               1 (2)
              2 LOAD_CONST               2 (4)
              4 LOAD_CONST               3 ((1, 3))
              6 BUILD_CONST_KEY_MAP      2
              8 RETURN_VALUE

從字節碼角度分析裝飾器的原理

如果你是一個pythoner 那麼你肯定或多或少聽說過裝飾器,這是一個python 的語法糖我們可以用它來做很多有趣的事情,例如在不修改原始程式碼的基礎之上給函數附加一些功能,比如說計算時間。

import time
 
def eval_time(func):
    
    def cal_time(*args, **kwargs):
        start = time.time()
        r = func(*args, **kwargs)
        end = time.time()
        return r, end - start
    return cal_time
 
 
@eval_time
def fib(n):
    a = 0
    b = 1
    while n > 0:
        n -= 1
        a, b = b, a + b
    return a

在上面的程式碼當中我們實作了一個計算斐波拉契數列的函數,除此之外還寫了一個eval_time 函數用於計算函數執行的時間,現在呼叫函數fib(10) ,程式的輸出如下:

>>>fib(10)
(55, 5.9604644775390625e-06)

可以看到實現了我們想要的效果。

現在我們使用一個更簡單的例子來模擬上面的程式碼結構,方便我們對上面函數執行的過程進行分析:

s = """
def decorator(func):
    print("Hello")
    return func
 
@decorator
def fib(n):
    pass
"""
dis.dis(s)

上面的dis 函數的輸出對應程式碼的位元組尺寸如下所示:

  2           0 LOAD_CONST               0(」,第2行> #               1 ('decorator' )
              4 MAKE_FUNCTION           0
              6 STORE_NAME               0 (裝飾者)
 
  6    ## 
  7            10 LOAD_CONST              2 (, line >; )<br>               12 LOAD_CONST               3 ('fib')<br>                16 CALL_FUNCTION            1<br>                    20 LOAD_CONST               4(無)<br>             22 RETURN_VALUE<br> <br>」,第2行>:<br>  3           0 LOAD_GLOBAL            0 LOAD_GLOBAL            0 LOAD_GLOBAL          1  LOAD_CONST               1 ( '你好' )<br>              4 CALL_FUNCTION            1<br>                      0 (func)<br>             10 RETURN_VALUE<br> <br>”,行6>:<br>  8           0 LOAD_CONST               0 (None)<br> 代碼對象對象,這個物件裡面主要是包含函數裝飾器的字節碼,主要是上面字節碼的第二塊內容。在執行完這條字節碼之後棧空間如下所示:<br><br><br><br>##執行完第二條指令LOAD_CONST 之後,將字串裝飾器載入到堆疊空間中。<br><br><br>

執行第三條指令MAKE_FUNCTION,這條字節碼的作用是在虛擬機器內部創建一個函數,函數的名稱為裝飾器,函數對應的字節碼放在前面壓入棧空間調用的代碼對象對像中,這條指令將會創建好的函數對象壓入堆疊中。

Python虛擬機字節碼之裝飾器怎麼實現

STORE_NAME,條字節碼貼上堆疊頂部的元素彈出,並且將co_names[oparg] 指向這個對象,在上面的字節碼涉及co_names[oparg]就是裝飾器。

Python虛擬機字節碼之裝飾器怎麼實現

LOAD_NAME,這條字節碼就是將co_names[oparg] 對應的名字指向的物件重新載入到堆疊空間使用,明白上面的裝飾器函數加入進行堆疊空間參考。

Python虛擬機字節碼之裝飾器怎麼實現##接下來的三條字節碼LOAD_CONST,LOAD_CONST 和MAKE_FUNCTION,在執行這三條字節碼之後,堆疊空間如下所示:

接下来的一条指令非常重要,这条指令便是装饰器的核心原理,CALL_FUNCTION 这条指令有一个参数 i,在上面的字节码当中为 1,也就是说从栈顶开始的前 i 个元素都是函数参数,调用的函数在栈空间的位置为 i + 1 (从栈顶往下数),那么在上面的情况下就是说调用 decorator 函数,并且将 fib 函数作为 decorator 函数的参数,decorator 函数的返回值再压入栈顶。在上面的代码当中 decorator 函数返回值也是一个函数,也就是 decorator 函数的参数,即 fib 函数。

Python虛擬機字節碼之裝飾器怎麼實現

接下来便是 STORE_NAME 字节码,这条字节码的含义我们在前面已经说过了,就是将栈顶元素弹出,保存到 co_names[oparg] 指向的对象当中,在上面的代码当中也就是将栈顶的对象保存到 fib 当中。栈顶元素 fib 函数是调用函数 decorator 的返回值。

看到这里就能够理解了原来装饰器的最根本的原理不就是函数调用嘛,比如我们最前面的用于计算函数执行时间的装饰器的原理就是:

fib = eval_time(fib)

将 fib 函数作为 eval_time 函数的参数,再将这个函数的返回值保存到 fib 当中,当然这个对象必须是可调用的,不然后面使用 fib() 就会保存,我们可以使用下面的代码来验证这个效果。

def decorator(func):
    return func()
 
 
@decorator
def demo():
    return "function demo return string : Demo"
 
print(demo)

执行上面的程序结果为:

function demo return string : Demo

可以看到 demo 已经变成了一个字符串对象而不再是一个函数了,因为 demo = decorator(demo),而在函数 decorator 当中返回值是 demo 函数自己的返回值,因此才打印了字符串。

以上是Python虛擬機字節碼之裝飾器怎麼實現的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除