python函數修飾器(decorator)
python語言本身具有豐富的功能和表達語法,其中修飾器是一個非常有用的功能。在設計模式中,decorator能夠在無需直接使用子類的方式來動態地修正一個函數,類或者類的方法的功能。當你希望在不修改函數本身的前提下擴展函數的功能時非常有用。
簡單地說,decorator就像一個wrapper一樣,在函數執行之前或者之後修改該函數的行為,而無需修改函數本身的代碼,這也是修飾器名稱的來由。
關於函數
在Python中,函數是first class citizen,函數本身也是對象,這意味著我們可以對函數本身做很多有意義的操作。
將函數賦值給變量:
def greet(name): return"hello "+name greet_someone = greet print greet_someone("John") # Outputs: hello John
函數內定義函數:
def greet(name): def get_message(): return "Hello " result = get_message()+name return result print greet("John") # Outputs: Hello John
函數可以作為參數傳給其他函數:
def greet(name): return"Hello " + name def call_func(func): other_name = "John" return func(other_name) print call_func(greet) # Outputs: Hello John
函數可以返回其他函數(函數產生函數):
def compose_greet_func(): def get_message(): return "Hello there!" return get_message greet = compose_greet_func() printgreet() # Outputs: Hello there!
內部函數可以訪問外部包scope(enclosing scope)
def compose_greet_func(name): def get_message(): return "Hello there "+name+"!" return get_message greet = compose_greet_func("John") print greet() # Outputs: Hello there John!
需要註意的是:這種情況下python僅僅允許"只讀"訪問外部scope的變量
開始創作我們的decorator
函數的修飾器就是已知函數的wrapper.將上述函數的好功能運用起來就能制作我們的decorator.
def get_text(name): return "lorem ipsum, {0} dolor sit amet".format(name) def p_decorate(func): def func_wrapper(name): return "<p>{0}</p>".format(func(name)) return func_wrapper my_get_text = p_decorate(get_text) print my_get_text("John") # <p>Outputs lorem ipsum, John dolor sit amet</p>
這就是我們的第一個修飾器。一個函數接收另一個函數作為參數,並且產生一個新的函數,修正參數函數的功能並添加新功能,並且返回一個"generated"新函數,這樣我們後面就可以在任何地方使用這個新創建的函數了。我們也可以將修飾器函數直接賦值給參數函數名本身,這樣就覆蓋了原來的函數!
get_text = p_decorate(get_text) print get_text("John") # Outputs lorem ipsum, John dolor sit amet
另外一點需要註意的是:被修飾的函數get_text具有一個name參數,我們必須在wrapper函數中傳入那個參數。
python的修飾符語法糖
在上面的例子中我們通過get_text=p_decorate(get_text)的方式覆蓋了get_text從而形成了有新功能的同名函數,這個顯得有點啰嗦,python提供了簡潔清晰的對應語法。比如:
def p_decorate(func): def func_wrapper(name): return "<p>{0}</p>".format(func(name)) return func_wrapper @p_decorate def get_text(name): return "lorem ipsum, {0} dolor sit amet".format(name) print get_text("John") # Outputs <p>lorem ipsum, John dolor sit amet</p>
在上面的例子代碼中,@符號後面的是修飾器本身,緊跟後面的則是將被修飾的函數(將隱含著賦值覆蓋操作)。這種語法等價於使用@後面的修飾器先對get_text修飾,並且返回產生的新函數替代被修飾的函數名。後面直接用被修飾的函數名調用,但是卻有了新的功能!
現在,我們希望再添加兩個其他的函數來修飾get_text分別再增加一個div和strong tag
def p_decorate(func): def func_wrapper(name): return "<p>{0}</p>".format(func(name)) return func_wrapper def strong_decorate(func): def func_wrapper(name): return "<strong>{0}</strong>".format(func(name)) return func_wrapper def div_decorate(func): def func_wrapper(name): return "<div>{0}</div>".format(func(name)) return func_wrapper # 基礎用法: get_text = div_decorate(p_decorate(strong_decorate(get_text))) #等價於: @div_decorate @p_decorate @strong_decorate def get_text(name): return "lorem ipsum, {0} dolor sit amet".format(name) print get_text("John") # Outputs <div><p><strong>lorem ipsum, John dolor sit amet</strong></p></div>
需要註意的是修飾器的順序是有關系的。如果順序不同,則結果也不同。
method修飾
python中類的方法是一個首參數為self指針的函數。我們可以和普通函數一樣去做修飾,但是需要註意的是必須在wrapper函數中考慮self指針參數。
def p_decorate(func): def func_wrapper(self): return "<p>{0}</p>".format(func(self)) return func_wrapper class Person(object): def __init__(self): self.name = "John" self.family = "Doe" @p_decorate def get_fullname(self): return self.name+" "+self.family my_person = Person() print my_person.get_fullname()
一個更好的方案是調整代碼使得我們的修飾器對於函數或者method同樣適用。這可以通過通過將args和*kwargs放到wrapper函數中作為參數來實現,這樣可以接受任意個數的參數或者keyword型參數。
def p_decorate(func): def func_wrapper(*args, **kwargs): return "<p>{0}</p>".format(func(*args, **kwargs)) return func_wrapper class Person(object): def __init__(self): self.name = "John" self.family = "Doe" @p_decorate def get_fullname(self): return self.name+" "+self.family my_person = Person() print my_person.get_fullname()
向decorator傳入參數
def tags(tag_name): def tags_decorator(func): def func_wrapper(name): return "<{0}>{1}</{0}>".format(tag_name, func(name)) return func_wrapper return tags_decorator @tags("p") def get_text(name): return "Hello "+name print get_text("John") # Outputs <p>Hello John</p>
在這個例子中,貌似又更加復雜了一點,但是帶來了更多的靈活性。decorator必須僅接受一個被修飾的函數為參數,這也是為什麽我們必須再外包裹一層從而接受那些額外的參數並且產生我們的decorator的原因。這個例子中tags函數是我們的decorator generator
調試decorated function
從上面的描述可知,decorators負責包裹被修飾的函數,這帶來一個問題就是如果要調試代碼可能有問題,因為wrapper函數並不會攜帶原函數的函數名,模塊名和docstring等信息,比如基於以上的例子,如果我們打印get_text.__name__則返回func_wrapper而不是get_text,原因就是__name__,__doc__,__module__這些屬性都被wrapper函數所(func_wrapper)重載。雖然我們可以手工重置(在func_wrapper),但是python提供了更好的辦法:
functools
functools模塊包含了wraps函數。wraps也是一個decorator,但是僅僅用於更新wrapping function(func_wrapper)的屬性為原始函數的屬性(get_text),看下面的代碼:
from functools import wraps def tags(tag_name): def tags_decorator(func): @wraps(func) def func_wrapper(name): return "<{0}>{1}</{0}>".format(tag_name, func(name)) return func_wrapper return tags_decorator @tags("p") def get_text(name): """returns some text""" return "Hello "+name print get_text.__name__ # get_text print get_text.__doc__ # returns some text print get_text.__module__ # __main__
何時使用decorator?
在上面的例子中僅僅羅列了修飾器的基礎用法,實際上這個機制是非常強大有用的,總的來說,decorator在你希望在不修改函數本身代碼的前提下擴展函數的功能時非常有用。
一個經典的例子timeout修飾函數:
https://wiki.python.org/moin/PythonDecoratorLibrary#Function_Timeout
timeout修飾符產生函數的定義:
import signal import functools class TimeoutError(Exception): pass def timeout(seconds, error_message = ‘Function call timed out‘): def decorated(func): def _handle_timeout(signum, frame): raise TimeoutError(error_message) def wrapper(*args, **kwargs): signal.signal(signal.SIGALRM, _handle_timeout) signal.alarm(seconds) try: result = func(*args, **kwargs) finally: signal.alarm(0) return result return functools.wraps(func)(wrapper) return decorated
使用:
import time @timeout(1, ‘Function slow; aborted‘) def slow_function(): time.sleep(5)
https://www.thecodeship.com/patterns/guide-to-python-function-decorators/
https://wiki.python.org/moin/PythonDecoratorLibrary
python函數修飾器(decorator)