裝飾器在 Python 中無處不在,功能強大。本篇介紹裝飾器的原理和用法,力求通俗易懂。
我們從一個簡單的例子開始,逐步展開。假設有一個函數,函數隨便做點啥:
def foo():
print("do something.")
foo
和 bar
在英語中相當於漢語的張三、李四,意思就是隨便給個名字。現在要給這個函數增加點功能,比如在函數調用前和調用後都打印一個輸出提示。我們日常開發中經常有這類橫向插入的需求,比如日誌、權限檢查等等。在 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()