裝飾器在 Python 中無處不在北滥,功能強(qiáng)大。本篇介紹裝飾器的原理和用法闸翅,力求通俗易懂再芋。
我們從一個(gè)簡單的例子開始,逐步展開坚冀。假設(shè)有一個(gè)函數(shù)济赎,函數(shù)隨便做點(diǎn)啥:
def foo():
print("do something.")
foo
和 bar
在英語中相當(dāng)于漢語的張三、李四,意思就是隨便給個(gè)名字∷狙担現(xiàn)在要給這個(gè)函數(shù)增加點(diǎn)功能华蜒,比如在函數(shù)調(diào)用前和調(diào)用后都打印一個(gè)輸出提示。我們?nèi)粘i_發(fā)中經(jīng)常有這類橫向插入的需求豁遭,比如日志、權(quán)限檢查等等贺拣。在 Java 中蓖谢,實(shí)現(xiàn)這個(gè)功能要用大代理模式,但在 Python 中卻異常簡單譬涡,我們只需要另外一個(gè)嵌套的函數(shù)闪幽,如下:
def outer(func):
def inner():
print("before execution.")
func()
print("after execution.")
return inner # 無括號(hào)
然后這樣使用:
if __name__ == "__main__":
proxy = outer(foo)
proxy()
程序輸出:
before execution.
do something.
after execution.
在 Python 中,一切都是對(duì)象涡匀,函數(shù)也是對(duì)象盯腌。所以
proxy = outer(foo)
就是把 foo
函數(shù)作為參數(shù)傳給 outer
函數(shù),而 outer
函數(shù)呢陨瘩,返回值為 inner
函數(shù)腕够,所以 proxy
變量也參照到 inner
函數(shù),再執(zhí)行 proxy()
語句舌劳,就是調(diào)用 inner
函數(shù)帚湘,我們看內(nèi)函數(shù) inner 的代碼,一共有三個(gè)語句:
print("before execution.") # 打印輸出
func() # 執(zhí)行 foo() 函數(shù)
print("after execution.") # 打印輸出
Python 提供了裝飾器語法糖·(@decorater_name
)甚淡,讓我們使用這種嵌套的函數(shù)更加簡單大诸。下面是變更后的代碼:
def outer(func):
def inner():
# 代碼同上,省略
return inner
@outer
def foo():
print("do something.")
if __name__ == "__main__":
foo()
@outer
是一個(gè)語法糖贯卦,也就是我們所說的裝飾器资柔,這個(gè) @outer
裝飾器就是告訴 Python,在調(diào)用 foo
函數(shù)的時(shí)候撵割,把它傳給 outer
函數(shù)作為參數(shù)贿堰。outer
函數(shù)通過上述機(jī)制,既保證調(diào)用 foo
函數(shù)睁枕,也通過它自己的代碼增強(qiáng)了 foo
的功能官边。
上述代碼為了說明機(jī)制,代碼極其簡化外遇,一般化的裝飾器代碼是這樣的:inner 函數(shù)有兩個(gè)參數(shù)注簿,以增加其靈活性。假設(shè)我們的需求實(shí)現(xiàn)一個(gè) logger 裝飾器跳仿,記錄函數(shù)被調(diào)用的時(shí)間:
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()
因?yàn)檠b飾器器外部函數(shù)需要以 function 作為參數(shù)诡渴,所以如果調(diào)用函數(shù)需要有信息需要傳給裝飾器,就需要再增加一層嵌套。
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')
裝飾器也可以基于類來實(shí)現(xiàn)妄辩,要求裝飾器類實(shí)現(xiàn) __init__()
方法和 __call__()
方法惑灵。類裝飾器實(shí)現(xiàn) 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()