Python裝飾器

裝飾器在Python中是一個強大的高級用法,並且在流行Python框架中變得越來越常見。經常會用到裝飾器來增強函數的行爲(動態的給一個對象添加一些額外的職責),包括記錄日誌,權限校驗,性能測試,數據封裝等。有了裝飾器,我們可以抽離出大量和函數功能本身無關的雷同代碼並繼續重用。

Python裝飾器有兩種:

  1. 函數裝飾器:管理函數調用和函數對象
  2. 類裝飾器:管理類實例和類自身

爲什麼使用裝飾器?

經常會遇到給函數或類增加新功能的場景,當然我們可以使用函數調用或者其它技術來實現,但是使用裝飾器意圖明確,最小化擴展代碼的冗餘,使用@語法糖,相對優雅。

裝飾器的原理是什麼?

我們先來看一個最簡單的裝飾器:

import time
from functools import wraps

def time_it(func):
    """
    輸出函數的運行時間
    :param func:
    :return:
    """
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func()
        end_time = time.time()
        process_time = end_time - start_time
        print(func.__name__, process_time)
        return result
    return wrapper

@time_it
def func_a():
    time.sleep(2)

對上述代碼進行解釋:

  1. time_it返回wrapper函數對象
  2. 使用time_it裝飾func_a函數
  3. 調用被裝飾的func_a函數會運行wrapper函數,func_a其實是wrapper的引用

原理:我們知道Python中一切皆對象,可以將函數作爲其它函數的返回值。可以看到,裝飾器的本質是一個函數,返回一個函數對象,通過"@"語法糖在包裝函數中引入裝飾器。

裝飾器的一個關鍵特性是,在被裝飾的函數定義之後立即執行。

@wraps

上述裝飾器中用到的了@wraps(func),在創建裝飾器時,一定要記得爲包裝函數添加functools庫中的@wraps裝飾器,以保證函數的元數據(包括函數名,函數註解等)不被丟失。

當我們需要訪問爲被裝飾器修飾的原包裝函數時,可以使用@wraps的__wrapped__屬性來訪問。

內置裝飾器

Python有三個內置裝飾器:@staticmathod、@classmethod和@property

  • @staticmethod:類的靜態方法,跟成員方法的區別是沒有self參數,並且可以在類不進行實例化的情況下調用。
  • @classmethod:跟成員方法的區別是接收的第一個參數不是self,而是cls(當前類的具體類型)
  • @property:表示可以直接通過類實例直接訪問的信息。

裝飾器嵌套

爲了支持多步驟的擴展,裝飾器語法允許我們向一個裝飾的函數或方法添加多個裝飾器,若多個裝飾器同時裝飾一個函數,那麼裝飾器的調用順序和@語法糖的聲明順序相反,也就是:

@decorator1
@decorator2
def func():
    pass

等效於:

func = decorator1(decorator2(func()))

裝飾器參數

函數裝飾器和類裝飾器都能接收參數,這些參數傳遞給了真正返回裝飾器的可調用對象,而裝飾器反過來又返回一個可調用對象。

裝飾器參數在裝飾發生之前就解析了,並且它們通常用來保持狀態信息供隨後的調用使用。

上述實例中,func_a()是沒有參數的,那如果添加參數的話,裝飾器該如何編寫以接收參數呢?可以在裝飾器中使用*args和**kwargs代替參數:

def time_it(func):
    """
    輸出函數的運行時間
    :param func:
    :return:
    """
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        process_time = end_time - start_time
        print(func.__name__, process_time)
        return result
    return wrapper

帶參數的裝飾器

我們有時候需要提供給被裝飾的函數特定的功能,需要在裝飾器中帶參數。比如在業務處理中我們需要限定函數的執行超時時間,由於每個函數所對應的超時時間不一樣,所以需要在裝飾器中帶參數以實現。

裝飾器的語法允許我們在調用時,提供其它參數,實現上述場景:

import time
import signal
import functools


def func_timeout(timeout):
    """
    超時時間裝飾器
    :param timeout:
    :return:
    """
    def decorator(func):
        def handler(signum, frame):
            raise RuntimeError("run %s timeout !" % func.__name__)

        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            signal.signal(signal.SIGALRM, handler)
            signal.alarm(timeout)
            func(*args, **kwargs)
            signal.alarm(0)
        return wrapper
    return decorator


@func_timeout(timeout=10)
def func():
    time.sleep(11)
    print("#" * 100)


if __name__ == '__main__':
    func()

類裝飾器

上述實例都是函數裝飾器,相比函數裝飾器,類裝飾器更加靈活,主要依靠類的__call__方法,當使用@形式將裝飾器附加到函數上時,就會調用此方法。

舉個例子:

class Foo(object):
    def __init__(self, func):
        self._func = func

    def __call__(self, *args, **kwargs):
        print("class decorator start")
        self._func(*args, **kwargs)
        print("class decorator end")


@Foo
def func():
    print("test123")

if __name__ == '__main__':
    func()

以上,代碼見 my github

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