在Python中,裝飾器一般用來修飾函數,實現公共功能,達到代碼復用的目的。在函數定義前加上@xxxx,然后函數就注入了某些行為,很神奇!然而,這只是語法糖而已。
場景
假設,有一些工作函數,用來對數據做不同的處理:
def work_bar(data): passdef work_foo(data): pass
我們想在函數調用前/后輸出日志,怎么辦?
傻瓜解法
logging.info('begin call work_bar')work_bar(1)logging.info('call work_bar done')如果有多處代碼調用呢?想想就怕!
函數包裝
傻瓜解法無非是有太多代碼冗余,每次函數調用都要寫一遍logging。可以把這部分冗余邏輯封裝到一個新函數里:
def smart_work_bar(data): logging.info('begin call: work_bar') work_bar(data) logging.info('call doen: work_bar')這樣,每次調用smart_work_bar即可:
smart_work_bar(1)# ...smart_work_bar(some_data)
通用閉包
看上去挺完美……然而,當work_foo也有同樣的需要時,還要再實現一遍smart_work_foo嗎?這樣顯然不科學呀!
別急,我們可以用閉包:
def log_call(func): def proxy(*args, **kwargs): logging.info('begin call: {name}'.format(name=func.func_name)) result = func(*args, **kwargs) logging.info('call done: {name}'.format(name=func.func_name)) return result return proxy這個函數接收一個函數對象(被代理函數)作為參數,返回一個代理函數。調用代理函數時,先輸出日志,然后調用被代理函數,調用完成后再輸出日志,最后返回調用結果。這樣,不就達到通用化的目的了嗎?——對于任意被代理函數func,log_call均可輕松應對。
smart_work_bar = log_call(work_bar)smart_work_foo = log_call(work_foo)smart_work_bar(1)smart_work_foo(1)# ...smart_work_bar(some_data)smart_work_foo(some_data)
第1行中,log_call接收參數work_bar,返回一個代理函數proxy,并賦給smart_work_bar。第4行中,調用smart_work_bar,也就是代理函數proxy,先輸出日志,然后調用func也就是work_bar,最后再輸出日志。注意到,代理函數中,func與傳進去的work_bar對象緊緊關聯在一起了,這就是閉包。
再提一下,可以覆蓋被代理函數名,以smart_為前綴取新名字還是顯得有些累贅:
work_bar = log_call(work_bar)work_foo = log_call(work_foo)work_bar(1)work_foo(1)
語法糖
先來看看以下代碼:
def work_bar(data): passwork_bar = log_call(work_bar)def work_foo(data): passwork_foo = log_call(work_foo)
雖然代碼沒有什么冗余了,但是看是去還是不夠直觀。這時候,語法糖來了~~~
新聞熱點
疑難解答