中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python裝飾器如何實現

發布時間:2023-05-04 09:33:30 來源:億速云 閱讀:84 作者:zzz 欄目:編程語言

這篇文章主要介紹“Python裝飾器如何實現”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Python裝飾器如何實現”文章能幫助大家解決問題。

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 (<code object decorator at 0x108068d40, file "<dis>", line 2>)
             2 LOAD_CONST               1 ('decorator')
             4 MAKE_FUNCTION            0
             6 STORE_NAME               0 (decorator)

 6           8 LOAD_NAME                0 (decorator)

 7          10 LOAD_CONST               2 (<code object fib at 0x1075c1710, file "<dis>", line 6>)
            12 LOAD_CONST               3 ('fib')
            14 MAKE_FUNCTION            0
            16 CALL_FUNCTION            1
            18 STORE_NAME               1 (fib)
            20 LOAD_CONST               4 (None)
            22 RETURN_VALUE

Disassembly of <code object decorator at 0x108068d40, file "<dis>", line 2>:
 3           0 LOAD_GLOBAL              0 (print)
             2 LOAD_CONST               1 ('Hello')
             4 CALL_FUNCTION            1
             6 POP_TOP

 4           8 LOAD_FAST                0 (func)
            10 RETURN_VALUE

Disassembly of <code object fib at 0x1075c1710, file "<dis>", line 6>:
 8           0 LOAD_CONST               0 (None)
             2 RETURN_VALUE

執行第一條指令 LOAD_CONST,這條指令主要是加載一個 code object 對象,這個對象里面主要是包含函數 decorator 的字節碼,主要是上面字節碼的第二塊內容。在執行完這條字節碼之后棧空間如下所示:

Python裝飾器如何實現

執行完第二條指令 LOAD_CONST 之后,會將字符串 decorator 加載進入棧空間當中。

Python裝飾器如何實現

執行第三條指令 MAKE_FUNCTION,這條字節碼的作用是在虛擬機內部創建一個函數,函數的名稱為 decorator,函數對應的字節碼則是在先前壓入棧空間當中的 code object 對象,這條指令還會將創建好的函數對象壓入棧中。

Python裝飾器如何實現

STORE_NAME,條字節碼會將棧頂的元素彈出,并且將 co_names[oparg] 指向這個對象,在上面的字節碼當中 co_names[oparg] 就是 decorator 。

Python裝飾器如何實現

LOAD_NAME,這條字節碼就是將 co_names[oparg] 對應的名字指向的對象重新加載進入棧空間當中,也就是上面的 decorator 函數加入進行棧空間當中。

Python裝飾器如何實現

接下來的三條字節碼 LOAD_CONST,LOAD_CONST 和 MAKE_FUNCTION,在執行這三條字節碼之后,棧空間如下所示:

Python裝飾器如何實現

接下來的一條指令非常重要,這條指令便是裝飾器的核心原理,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裝飾器如何實現”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

杨浦区| 永丰县| 枣庄市| 永修县| 霞浦县| 新乡市| 抚松县| 格尔木市| 成武县| 普兰店市| 浦北县| 泰兴市| 平昌县| 彭水| 曲阜市| 全南县| 贵港市| 呈贡县| 塔河县| 赣榆县| 济源市| 谢通门县| 大港区| 大庆市| 松江区| 乾安县| 酉阳| 同德县| 林周县| 武山县| 务川| 五华县| 扬中市| 九龙县| 怀集县| 卢氏县| 新河县| 乐至县| 斗六市| 凤城市| 玉溪市|