項目地址:https://git.io/pytipshtml
Python 的修飾器是一種語法糖(Syntactic Sugar),也就是說:python
@decorator @wrap def func(): pass
是下面語法的一種簡寫:git
def func(): pass func = decorator(wrap(func))
關於修飾器的兩個主要問題:github
修飾器用來修飾誰shell
誰能夠做爲修飾器編程
修飾器最多見的用法是修飾新定義的函數,在 0x0d 上下文管理器中提到上下文管理器主要是爲了更優雅地完成善後工做,而修飾器一般用於擴展函數的行爲或屬性:flask
def log(func): def wraper(): print("INFO: Starting {}".format(func.__name__)) func() print("INFO: Finishing {}".format(func.__name__)) return wraper @log def run(): print("Running run...") run()
INFO: Starting run Running run... INFO: Finishing run
除了修飾函數以外,Python 3.0 以後增長了對新定義類的修飾(PEP 3129),可是對於類別屬性的修改能夠經過 Metaclasses
或繼承來實現,而新增長的類別修飾器更可能是出於 Jython 以及 IronPython 的考慮,但其語法仍是很一致的:app
from time import sleep, time def timer(Cls): def wraper(): s = time() obj = Cls() e = time() print("Cost {:.3f}s to init.".format(e - s)) return obj return wraper @timer class Obj: def __init__(self): print("Hello") sleep(3) print("Obj") o = Obj()
Hello Obj Cost 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 <html>! LOG: Baking Tag <body>! LOG: Baking Tag <div>! <html><body><div>Hello</div></body></html>
在實際使用過程當中,咱們可能須要向修飾器傳遞參數,也有可能須要向被修飾的函數(或類)傳遞參數。按照語法約定,只要修飾器 @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! {'/': <function index at 0x10706f730>, '/home': <function home at 0x10706f8c8>}
向被修飾的函數傳遞參數,要看咱們的修飾器是如何做用的,若是像上面這個例子同樣未執行被修飾函數只是將其原模原樣地返回,則不須要任何處理(這就把函數當作普通的值同樣看待便可):
@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 @log def run(hello = "world"): print("Hello {}".format(hello)) run("Python")
INFO: Start Logging Hello Python INFO: 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 <body>! <lambda> 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>! body return body content!
有趣的是 update_wrapper
的用法自己就很像是修飾器,所以 functools.wraps
就利用 functools.partial
(還記得函數式編程中的偏應用吧!)將其變成一個修飾器:
from functools import update_wrapper, partial def 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 @log def run(): """ Docs' of run """ print("Running run...") print(run.__name__) print(run.__doc__)
run Docs' of run
歡迎關注公衆號 PyHub!