Python3之裝飾器(Decorator)進階

日期:2020年3月2日
作者:Commas
註釋:學習就是爲了忘記,接上一章《Python3之裝飾器(Decorator)淺談》,現在講一下Python裝飾器進階;
如果您想了解更多有關Python的知識,那麼請點《我的Python淺談系列目錄》



一、裝飾器的進階

書接上文,我們現在來看看裝飾帶參數函數的裝飾器,如下:

import time


def calculate_time(f):
    """裝飾器函數"""
    # (1)採用*args, **kwargs來接收參數
    # def wrapper():
    def wrapper(*args, **kwargs):
        """這是wrapper函數的說明文檔"""
        start_time = time.time()
        # (2)被裝飾函數f接受*args, **kwargs參數 
        # f()
        f(*args, **kwargs)
        end_time = time.time()
        delta_time = end_time - start_time
        print("{}的執行時間:{}".format(f, delta_time))
    return wrapper


@calculate_time
def func(param):
    """這是func函數的說明文檔"""
    time.sleep(1)
    print("執行了{}()函數,其中param={}".format(func.__name__, param))


func(1)

# =======控制檯輸出結果=======
執行了wrapper()函數,其中param=1
<function func at 0x000001F6E2F698C8>的執行時間:1.0006613731384277

如果被裝飾的函數有返回值,那麼就應該修改爲:

def calculate_time(f):
    """裝飾器函數"""
    def wrapper(*args, **kwargs):
        """這是wrapper函數的說明文檔"""
        start_time = time.time()
        # (2)接收返回值
        # f(*args, **kwargs)
        ret = f(*args, **kwargs)
        end_time = time.time()
        delta_time = end_time - start_time
        print("{}的執行時間:{}".format(f, delta_time))
        # (3)返回返回值
        return ret
    return wrapper


@calculate_time
def func(param):
    """這是func函數的說明文檔"""
    time.sleep(1)
    # (1)被裝飾的函數有返回值
    # print("執行了{}()函數,其中param={}".format(func.__name__, param))
    return "執行了{}()函數,其中param={}".format(func.__name__, param)


# func(1)
ret = func(1)
print(ret)

# =======控制檯輸出結果=======
<function func at 0x0000019F789698C8>的執行時間:1.0038354396820068
執行了wrapper()函數,其中param=1

無論是裝飾帶參數函數的裝飾器,還是裝飾有返回值函數的裝飾器,都有一個不完美的地方,那就是被裝飾後的函數,函數的內置屬性(如__name__)都發生了變化。如果想保證被裝飾的函數做到“不變”,那麼我們要不自己遍歷,將對應的值更新,要麼可以藉助@wraps,如下:

import time
# (1)從模塊functools導入wraps
from functools import wraps


def calculate_time(f):
    """裝飾器函數"""
    # (2)使用wraps
    @wraps(f)
    def wrapper(*args, **kwargs):
        """這是wrapper函數的說明文檔"""
        start_time = time.time()
        ret = f(*args, **kwargs)
        end_time = time.time()
        delta_time = end_time - start_time
        print("{}的執行時間:{}".format(f, delta_time))
        return ret
    return wrapper


@calculate_time
def func(param):
    """這是func函數的說明文檔"""
    time.sleep(1)
    return "執行了{}()函數,其中param={}".format(func.__name__, param)


# func(1)
ret = func(1)
print(ret)

# =======控制檯輸出結果=======
<function func at 0x0000020C3DAC0048>的執行時間:1.0006630420684814
執行了func()函數,其中param=1

結果完美,一切OK!
但是@wraps(f)是個什麼梗,還可以給裝飾器傳參,且待我慢慢說來,這個實際是分兩部分執行,如下:

  1. "@"右邊的wraps(f)先執行,並給個函數返回值,即wraps = wraps(f);
  2. 然後再執行,我們熟悉的@wraps,此時的wraps = wraps(f);

那麼我們來實現一個類似的吧,@calculate_time(True),用來控制是否計算函數執行時間,如下:

import time
from functools import wraps


def calculate_time(flag):
    def inner(f):
        """裝飾器函數"""
        @wraps(f)
        def wrapper(*args, **kwargs):
            """這是wrapper函數的說明文檔"""
            if flag:
                start_time = time.time()
                ret = f(*args, **kwargs)
                end_time = time.time()
                delta_time = end_time - start_time
                print("{}的執行時間:{}".format(f, delta_time))
            else:
                ret = f(*args, **kwargs)
            return ret
        return wrapper
    return inner


@calculate_time(True)
def func(param):
    """這是func函數的說明文檔"""
    time.sleep(1)
    return "執行了{}()函數,其中param={}".format(func.__name__, param)

ret = func(1)
print(ret)

# =======控制檯輸出結果=======
<function func at 0x00000162250300D0>的執行時間:1.0001797676086426
執行了func()函數,其中param=1

二、多個裝飾器執行順序

在這裏插入圖片描述

@裝飾器A
@裝飾器B
def 被裝飾函數():
	pass

從圖和代碼可以知,函數的執行過程是:
1(裝飾器A)→2(裝飾器B)→3(裝飾器B)→4(裝飾器A)


版權聲明:本文爲博主原創文章,如需轉載,請給出:
原文鏈接:https://blog.csdn.net/qq_35844043/article/details/104609540

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章