淺析decorator

從功能上來說,decorator是為了在代碼運行期間動態(tài)增加代碼功能的一種方案(即:我們要增強函數(shù)的功能,但是又不希望修改函數(shù)的定義);
從實現(xiàn)上來說,decorator is a function that accepts a function as input and returns a new function as output.

先上代碼來舉個栗子:


example
import time
from functools import wraps

def timethis(func):
    '''
    Decorator that reports the executions time.
    '''
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end - start)
        return result
    return wrapper

@timethis
def countdown(n):
    '''
    Counts down
    '''
    while n > 0:
        n -= 1

分析開始
我們先來看原始函數(shù):

def countdown(n):
    '''
    Counts down
    '''
    while n > 0:
        n -= 1

這個函數(shù)的功能很簡單,就是把輸入的n減到0,也沒有輸出,所以沒什么卵用。接下來我們決定開始增強它的功能,讓它有卵用!??!

我決定給這個函數(shù)增加:
1.打印函數(shù)名字的功能
2.打印整個函數(shù)運行時間的功能

這兩個功能我已經(jīng)寫好了,我把它寫在了名為timethis的這個函數(shù)里面。我現(xiàn)在就來改造原函數(shù)!代碼如下:

@timethis
def countdown(n):
    '''
    Counts down
    '''
    while n > 0:
        n -= 1

執(zhí)行一下:print(countdown(100000))
輸出結果:

countdown 0.014000892639160156
None

(不要在意這個None,之所以出現(xiàn)None是因為我們的coundown函數(shù)沒有寫返回值啊親:P)

你現(xiàn)在肯定想問我加了什么特技,居然只寫了一個@timethis就實現(xiàn)了增強函數(shù)功能這么神奇的事情!

講解如下
@這個符號在微博里表示“點名”,或者“提醒某某人”的意思,在這里也是一樣的。
@timethis寫在了countdown函數(shù)定義的上面,就相當于countdown函數(shù)在微博上@了一下timethis,然后跟他說,“喂喂喂,內(nèi)個誰,timethis啊,你幫我個忙啊?!?/strong>

那么countdown函數(shù)具體是如何告訴timethis自己的需求的呢?timethis怎么知道要幫什么忙呢?

答案是:這些需求都已經(jīng)寫在了timethis函數(shù)的函數(shù)體里面,代碼如下:

def timethis(func):
    '''
    Decorator that reports the executions time.
    '''
    @wraps(func)  #這句代碼先不要考慮,我們在本文章的最后給大家分析
    def wrapper(*args, **kwargs):
        start = time.time()  #獲取系統(tǒng)時間
        result = func(*args, **kwargs)
        end = time.time()  #我們獲取了兩次系統(tǒng)時間,它們相減得到的值就是函數(shù)的運行時間
        print(func.__name__, end - start)
        return result
    return wrapper

接下來我們一步步地還原這個函數(shù)的構建過程(注釋我就不再寫一遍了啊=_=):

1.我們接收一個沒卵用的函數(shù),對它進行改造后,返回一個niubility的新函數(shù)。

def timethis(func):  #func就是待改造的函數(shù)
    XXXXXXXXX   #對原函數(shù)func進行改造的代碼
    return 一個增強后的新函數(shù)

2.由于我們返回了一個新函數(shù),所以理所當然地,我們要在函數(shù)體中定義這個新函數(shù)。(我們給這個增強后的函數(shù)取一個名字wrapper吧,這個名字取得還是挺形象的呢:P
所以代碼現(xiàn)在變成了如下的樣子:

def timethis(func):
    def wrapper(*args, **kwargs):  #參數(shù)表寫成這個樣子,表示我們可以將任意參數(shù)傳入這個函數(shù),在本文章末尾我會對此用法進行簡要說明
        XXXXXXXXX  #增加的新功能的代碼
        return func(*args, **kwargs)  #返回之前函數(shù)原有功能的執(zhí)行結果(雖然我們要增加新功能,但是函數(shù)原有的功能也不能丟了呀,所以這段代碼是必要的)
    return wrapper  #這句話返回了強大的新函數(shù)

3.進一步地,函數(shù)變成了如下的樣子:

def timethis(func):
    def wrapper(*args, **kwargs):
        start = time.time()  #獲取系統(tǒng)時間
        result = func(*args, **kwargs)
        end = time.time()  #我們獲取了兩次系統(tǒng)時間,它們相減得到的值就是函數(shù)的運行時間
        print(func.__name__, end - start)
        return result
    return wrapper

到目前為止,我們基本上完成了decorator的編寫和使用。
我們談一下前面遺留的幾個重要問題:

1.為什么要在wrapper函數(shù)的上面寫@wraps(func) ?
答:為了保留原函數(shù)的一些原始信息,或者說保留函數(shù)的metedata.

當我們使用裝飾器時(如下),

@timethis
def countdown(n):
    ...

我們相當于執(zhí)行了如下代碼:

def countdown(n):
    ...
countdown = timethis(countdown)

也就是說,我們返回了一個新函數(shù)后,還用舊函數(shù)的名字來使用它。那么問題來了,當我們通過這個函數(shù)來訪問某些信息時(具體訪問哪些信息我們接下來討論),我們原本想訪問舊函數(shù)的信息,可是由于被覆蓋,我們只能訪問到新函數(shù)的信息,這顯然不是我們想要的結果。
從另一個角度想,我們使用裝飾器只是想增強原函數(shù)的功能,沒想讓新函數(shù)取而代之?。∥覀冃枰倪€是舊函數(shù)??!
好了,wrapper函數(shù)上面寫的@wraps(func)就解決了這個問題。
我們來訪問一些信息,以便驗證我們剛才說的話:

condition 1:沒寫@wraps(func)時:

執(zhí)行如下代碼:

print(countdown.__name__)
print(countdown.__doc__)

輸出如下:

wrapper
None

condition 2:寫了@wraps(func)以后:

執(zhí)行如下代碼:

print(countdown.__name__)
print(countdown.__doc__)

輸出如下:

countdown

    Counts down

由此可見,@wraps(func)是非常必要的。補充一點,要想使用@wraps(func)還需要在代碼的開頭寫上from functools import wraps這么一句。

2.關于參數(shù)表(*args, **kwargs)
* 代表可變參數(shù),** 代表關鍵字參數(shù)。至于參數(shù)的名字,其實無所謂,不過通常我們使用args來命名可變參數(shù),用kw或kwargs來命名關鍵字參數(shù)。
這里我講的不是很具體,不懂的話請百度“python可變參數(shù)”,“python關鍵字參數(shù)”。

總結
本文的內(nèi)容就和本文的標題一樣,只是對decorator的一個粗淺的介紹。
還有很多有用的內(nèi)容我們限于篇幅并沒有提到,那么就請期待我的下一篇關于decorator的文章吧!

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • Python進階框架 希望大家喜歡,點贊哦首先感謝廖雪峰老師對于該課程的講解 一、函數(shù)式編程 1.1 函數(shù)式編程簡...
    Gaolex閱讀 6,016評論 6 53
  • 前言 Python的修飾器的英文名叫Decorator,當你看到這個英文名的時候,你可能會把其跟Design Pa...
    linheimx閱讀 662評論 0 4
  • 函數(shù)式編程就是一種抽象程度很高的編程范式,純粹的函數(shù)式編程語言編寫的函數(shù)沒有變量,因此,任意一個函數(shù),只要輸入是確...
    齊天大圣李圣杰閱讀 1,649評論 0 2
  • 要點: 函數(shù)式編程:注意不是“函數(shù)編程”,多了一個“式” 模塊:如何使用模塊 面向對象編程:面向對象的概念、屬性、...
    victorsungo閱讀 1,700評論 0 6
  • 1. Android系統(tǒng)架構 四層架構: Linux內(nèi)核層、系統(tǒng)運行庫層、應用框架層、應用層。 如圖所示: 1.1...
    開心wonderful閱讀 1,514評論 0 6

友情鏈接更多精彩內(nèi)容