理解和使用Python裝飾器

裝飾器在 Python 中無處不在,功能強大。本篇介紹裝飾器的原理和用法,力求通俗易懂。

我們從一個簡單的例子開始,逐步展開。假設有一個函數,函數隨便做點啥:

def foo():
    print("do something.")

foobar 在英語中相當於漢語的張三、李四,意思就是隨便給個名字。現在要給這個函數增加點功能,比如在函數調用前和調用後都打印一個輸出提示。我們日常開發中經常有這類橫向插入的需求,比如日誌、權限檢查等等。在 Java 中,實現這個功能要用大代理模式,但在 Python 中卻異常簡單,我們只需要另外一個嵌套的函數,如下:

def outer(func):       
    def inner():
        print("before execution.")
        func()
        print("after execution.")

    return inner # 無括號

然後這樣使用:

if __name__ == "__main__":    
    proxy = outer(foo)
    proxy()

程序輸出:

before execution.
do something.
after execution.

在 Python 中,一切都是對象,函數也是對象。所以

proxy = outer(foo)

就是把 foo 函數作爲參數傳給 outer 函數,而 outer 函數呢,返回值爲 inner 函數,所以 proxy 變量也參照inner 函數,再執行 proxy() 語句,就是調用 inner 函數,我們看內函數 inner 的代碼,一共有三個語句:

print("before execution.")      # 打印輸出
func()                          # 執行 foo() 函數
print("after execution.")       # 打印輸出

Python 提供了裝飾器語法糖·(@decorater_name),讓我們使用這種嵌套的函數更加簡單。下面是變更後的代碼:

def outer(func):       
    def inner():
        # 代碼同上,省略
    return inner 

@outer
def foo():
    print("do something.")

if __name__ == "__main__":    
    foo()

@outer 是一個語法糖,也就是我們所說的裝飾器,這個 @outer 裝飾器就是告訴 Python,在調用 foo 函數的時候,把它傳給 outer 函數作爲參數。outer 函數通過上述機制,既保證調用 foo 函數,也通過它自己的代碼增強了 foo 的功能

上述代碼爲了說明機制,代碼極其簡化,一般化的裝飾器代碼是這樣的:inner 函數有兩個參數,以增加其靈活性。假設我們的需求實現一個 logger 裝飾器,記錄函數被調用的時間:

from datetime import datetime

def logger(func):
    def wrapper(*args, **kwargs):
        print ('[INFO] {}, function "{}" was called '.format(datetime.now(), func.__name__))
        return func(*args, **kwargs)
    return wrapper

@logger
def foo():
    print("do something.")

if __name__ == "__main__":    
    foo()

因爲裝飾器器外部函數需要以 function 作爲參數,所以如果調用函數需要有信息需要傳給裝飾器,就需要再增加一層嵌套。

from datetime import datetime

def logger(msg):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print('[INFO] {}, "{}" was called with message "{}"'.format(
                datetime.now(), func.__name__, msg))
            return func(*args, **kwargs)
        return wrapper
    return decorator

@logger("Maybe bored.")
def foo(name):
    print("do something, " + name)

foo('Johnny')

裝飾器也可以基於類來實現,要求裝飾器類實現 __init__() 方法和 __call__() 方法。類裝飾器實現 logger 的代碼如下:

from datetime import datetime

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

    def __call__(self, *args, **kwargs):
        print ('[INFO] {}, function "{}" was called '.format(
            datetime.now(), self.func.__name__))

        return self.func(*args, **kwargs)

@logger
def foo():
    print("do something.")

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