@decorator@wrapdef func(): pass
是下面語法的一種簡寫:
def func(): passfunc = decorator(wrap(func))
關于修飾器的兩個主要問題:
修飾器用來修飾誰
誰可以作為修飾器
修飾器最常見的用法是修飾新定義的函數,在 0x0d 上下文管理器中提到上下文管理器主要是為了 更優雅地完成善后工作,而修飾器通常用于擴展函數的行為或屬性:
def log(func): def wraper(): print("INFO: Starting {}".format(func.__name__)) func() print("INFO: Finishing {}".format(func.__name__)) return wraper@logdef run(): print("Running run...")run()
INFO: Starting runRunning run...INFO: Finishing run
除了修飾函數之外,Python 3.0 之后增加了對新定義類的修飾(PEP 3129),但是對于類別屬性的修改可以通過 Metaclasses或繼承來實現,而新增加的類別修飾器更多是出于 Jython 以及 IronPython 的考慮,但其語法還是很一致的:
from time import sleep, timedef timer(Cls): def wraper(): s = time() obj = Cls() e = time() print("Cost {:.3f}s to init.".format(e - s)) return obj return wraper@timerclass Obj: def __init__(self): print("Hello") sleep(3) print("Obj")o = Obj()
HelloObjCost 3.005s to init.
上面兩個例子都是以函數作為修飾器,因為函數才可以被調用(callable) decorator(wrap(func))。除了函數之外,我們也可以定義可被調用的類,只要添加 __call__方法即可:
class HTML(object): """ Baking HTML Tags! """ def __init__(self, tag="p"): print("LOG: Baking Tag <{}>!".format(tag)) self.tag = tag def __call__(self, func): return lambda: "<{0}>{1}{0}>".format(self.tag, func(), self.tag) @HTML("html")@HTML("body")@HTML("div")def body(): return "Hello"print(body())
LOG: Baking Tag !LOG: Baking Tag !LOG: Baking Tag !Hello
在實際使用過程中,我們可能需要向修飾器傳遞參數,也有可能需要向被修飾的函數(或類)傳遞參數。按照語法約定,只要修飾器 @decorator中的 decorator是可調用即可, decorator(123)如果返回一個新的可調用函數,那么也是合理的,上面的 @HTML('html')即是一例,下面再以 flask 的路由修飾器為例說明如何傳遞參數給修飾器:
RULES = {}def route(rule): def decorator(hand): RULES.update({rule: hand}) return hand return decorator @route("/")def index(): print("Hello world!")def home(): print("Welcome Home!")home = route("/home")(home)index()home()print(RULES)
Hello world!Welcome Home!{'/':, '/home': }
向被修飾的函數傳遞參數,要看我們的修飾器是如何作用的,如果像上面這個例子一樣未執行被修飾函數只是將其原模原樣地返回,則不需要任何處理(這就把函數當做普通的值一樣看待即可):
@route("/login")def login(user = "user", pwd = "pwd"): print("DB.findOne({{{}, {}}})".format(user, pwd))login("hail", "python")
DB.findOne({hail, python})
如果需要在修飾器內執行,則需要稍微變動一下:
def log(f): def wraper(*args, **kargs): print("INFO: Start Logging") f(*args, **kargs) print("INFO: Finish Logging") return wraper@logdef run(hello = "world"): print("Hello {}".format(hello))run("Python")
INFO: Start LoggingHello PythonINFO: Finish Logging
由于修飾器將函數(或類)進行包裝之后重新返回: func = decorator(func),那么有可能改變原本函數(或類)的一些信息,以上面的 HTML修飾器為例:
@HTML("body")def body(): """ return body content """ return "Hello, body!"print(body.__name__)print(body.__doc__)
LOG: Baking Tag !None
因為 body = HTML("body")(body),而 HTML("body").__call__()返回的是一個 lambda函數,因此 body已經被替換成了 lambda,雖然都是可執行的函數,但原來定義的 body中的一些屬性,例如 __doc__/ __name__/ __module__都被替換了(在本例中 __module__沒變因為都在同一個文件中)。為了解決這一問題 Python 提供了 functools標準庫,其中包括了 update_wrapper和 wraps兩個方法(源碼)。其中 update_wrapper就是用來將原來函數的信息賦值給修飾器中返回的函數:
from functools import update_wrapper"""functools.update_wrapper(wrapper, wrapped[, assigned][, updated])"""class HTML(object): """ Baking HTML Tags! """ def __init__(self, tag="p"): print("LOG: Baking Tag <{}>!".format(tag)) self.tag = tag def __call__(self, func): wraper = lambda: "<{0}>{1}{0}>".format(self.tag, func(), self.tag) update_wrapper(wraper, func) return wraper @HTML("body")def body(): """ return body content! """ return "Hello, body!"print(body.__name__)print(body.__doc__)
LOG: Baking Tag !body return body content!
有趣的是 update_wrapper的用法本身就很像是修飾器,因此 functools.wraps就利用 functools.partial(還記得函數式編程中的偏應用吧!)將其變成一個修飾器:
from functools import update_wrapper, partialdef my_wraps(wrapped): return partial(update_wrapper, wrapped=wrapped)def log(func): @my_wraps(func) def wraper(): print("INFO: Starting {}".format(func.__name__)) func() print("INFO: Finishing {}".format(func.__name__)) return wraper@logdef run(): """ Docs' of run """ print("Running run...")print(run.__name__)print(run.__doc__)
run Docs' of run
Python修飾器的函數式編程
聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com