Decorator
首先,我們來(lái)認(rèn)識(shí)一下裝飾器是什么:
裝飾器是給現(xiàn)有的模塊增添新的小功能
(在不改變?cè)心K功能的基礎(chǔ)上)
假如我有個(gè)簡(jiǎn)單筆,它只能用一種顏色進(jìn)行寫(xiě)字
我現(xiàn)在給它加上一只筆芯,它能換種顏色寫(xiě)字(又能換回來(lái)~)
這就是裝飾器的樸素比喻
一、初探裝飾器
手動(dòng)寫(xiě)個(gè)裝飾器吧
#自定義裝飾函數(shù)
def decorator(fn):
def wrapper(*args):
#這里裝飾器的作用是在函數(shù)調(diào)用前增加一句話表示裝飾成功
print("this is decorator fo %s" % fn.__name__)
fn(*args)
return wrapper
def hello(name):
print('hello,%s' %name)
if __name__=="__main__":
#用賦值的形式進(jìn)行裝飾器
hello=decorator(hello)
hello("cool")
輸出結(jié)果為:

首先,我們要知道,在python中,函數(shù)也是一種對(duì)象(萬(wàn)物皆對(duì)象?。?/p>
函數(shù)可以賦值給一個(gè)變量(學(xué)過(guò)C語(yǔ)言的可以聯(lián)想下函數(shù)指針)
函數(shù)可以定義在另一個(gè)函數(shù)內(nèi)部
這也意味著一個(gè)函數(shù)可以返回另一個(gè)函數(shù)
hello=decorator(hello)
這一句代碼中,將hello函數(shù)作為變量傳入decorator裝飾器中,然后hello方法在decorator中的函數(shù)wrapper函數(shù)實(shí)現(xiàn),同時(shí)包裝新的功能,將新的函數(shù)wrapper作為變量返回 ,所以hello的新值是 經(jīng)過(guò)decorator裝飾的wrapper新方法。
所以,裝飾器裝飾函數(shù)的時(shí)候,將函數(shù)作為變量傳入裝飾器內(nèi)部,實(shí)際調(diào)用的是裝飾器內(nèi)部的函數(shù)(添加新功能之后的函數(shù))
二、 @語(yǔ)法糖
Python 中裝飾器語(yǔ)法并不用每次都用賦值語(yǔ)句。
在函數(shù)定義的時(shí)候就加上@+裝飾器名字即可
再來(lái)我們剛才的例子吧:
def decorator(fn):
def wrapper(*args):
print("this is decorator fo %s" % fn.__name__)
fn(*args)
return wrapper
@decorator
def hello(name):
print('hello,%s' %name)
if __name__=="__main__":
hello("cool")
2.裝飾器的順序:
比如我們有兩個(gè)裝飾器:
@decorator_one
@decorator_two
def hello()
pass
這句代碼實(shí)際上類似于:
hello=decorator_one(decorator_two(hello))
兩個(gè)裝飾器一層層地往外裝飾
3.帶參數(shù)的裝飾器
我們說(shuō)過(guò),裝飾器其實(shí)也是一種函數(shù),所以它自身也是能帶參數(shù)的
@decorator(arg1, arg2)
def func():
pass
類似于
func = decorator(arg1,arg2)(func)
來(lái)個(gè)實(shí)際點(diǎn)的例子吧:
我們手寫(xiě)html的時(shí)候需要各種補(bǔ)全(那個(gè)用編輯器的當(dāng)然爽得飛起?。?br>
但是,如果是在python中用字符串去表示html標(biāo)簽的時(shí)候,就~坑爹了
。總不能每個(gè)標(biāo)簽我都寫(xiě)一個(gè)方法吧
最方便的方法,寫(xiě)一個(gè)帶參數(shù)的裝飾器!
HTML.py
def makeHtmlTag(tag, *args, **kwargs):
def real_decorator(fn):
css_class = " class='{0}'".format(kwargs["css_class"]) \
if "css_class" in kwargs else ""
def wrapped(*args, **kwargs):
return "<"+tag+css_class+">" + fn(*args, **kwargs) + "</"+tag+">"
return wrapped
return real_decorator
@makeHtmlTag(tag="b", css_class="bold_css")
@makeHtmlTag(tag="i", css_class="italic_css")
def hello():
return "hello world"
print(hello())
print(hello())
運(yùn)行結(jié)果:

關(guān)于幾點(diǎn)說(shuō)明:
(1)在裝飾器makeHtmlTag中,*args代表了參數(shù)元組,假如你傳入的的參數(shù)分別是1,2,3,4,則args=(1,2,3,4)
**kwds則參數(shù)字典,返回一個(gè)key為參數(shù)變量名,value為變量值的字典
這樣我們就可以很方便地不用改動(dòng)函數(shù)本身去獲取函數(shù)傳遞的參數(shù)并進(jìn)行裝飾了
(2)使用裝飾器的時(shí)候有個(gè)缺陷就是不能在過(guò)程中更改某個(gè)裝飾器的參數(shù)值(比如該例子中 hello 的便簽就永遠(yuǎn)是b,i了)
如果你覺(jué)得這樣寫(xiě)太!麻!煩!了!什!么!鬼!
為什么我要在函數(shù)體中再定義一個(gè)函數(shù)體?。。?!
難道還要我一層層剝開(kāi)你的心嗎?
4. 用類的方式去寫(xiě)一個(gè)裝飾器
class makeHtmlTagClass(object):
def __init__(self, tag, css_class=""):
self._tag = tag
self._css_class = " class='{0}'".format(css_class) \
if css_class !="" else ""
def __call__(self, fn):
def wrapped(*args, **kwargs):
return "<" + self._tag + self._css_class+">" \
+ fn(*args, **kwargs) + "</" + self._tag + ">"
return wrapped
@makeHtmlTagClass(tag="b", css_class="bold_css")
@makeHtmlTagClass(tag="i", css_class="italic_css")
def hello(name):
return "Hello, {}".format(name)
print hello("Hao Chen")
關(guān)于說(shuō)明:
(1)我們將整個(gè)類作為一個(gè)裝飾器,工作流程:
通過(guò)__init__()方法初始化類
通過(guò)__call__()方法調(diào)用真正的裝飾方法
(2)當(dāng)裝飾器有參數(shù)的時(shí)候,init() 成員就不能傳入fn了,而fn是在call的時(shí)候傳入的。(fn代表要裝飾的函數(shù))
decorator萬(wàn)能的?
No!No!No!
有時(shí)候我想加入日志系統(tǒng)。來(lái)記錄我某個(gè)函數(shù)的運(yùn)行情況。
import time
def logger(fn):
def wrapper(*args,**kwargs):
ts=time.time()
print('start run the function %s' % fn.__name__)
result=fn(*args,**kwargs)
te=time.time()
print('run end! it continue %.2f '%(te-ts))
return result
return wrapper
@logger
def hello():
print('start running')
time.sleep(2)
return 2
hello()
print(hello.__name__)
運(yùn)行結(jié)果:

W T F?
我的hello.__name__不是應(yīng)該是hello嗎?
唯一解釋:就想一開(kāi)始說(shuō)的,裝飾器原理:
hello=decorator(hello)
hello實(shí)際上已經(jīng)變成了經(jīng)過(guò)裝飾器修飾的方法了
(主公,我身在曹營(yíng)心在漢呀!?。。。?/strong>
怎么辦?
Python的functool包中提供了一個(gè)叫wrap的decorator來(lái)消除這樣的副作用
新版logger.py
from functools import wraps
import time
def logger(fn):
@wraps(fn)
def wrapper(*args,**kwargs):
ts=time.time()
print('start run the function %s' % fn.__name__)
result=fn(*args,**kwargs)
te=time.time()
print('run end! it continue %.2f '%(te-ts))
return result
return wrapper
@logger
def hello():
print('start running')
time.sleep(2)
return 2
hello()
print(hello.__name__)
運(yùn)行結(jié)果:

5.裝飾器獲取參數(shù)的值
比如我某個(gè)函數(shù)是批量運(yùn)行的,我需要加個(gè)日志系統(tǒng)來(lái)知道這個(gè)函數(shù)進(jìn)行了多少次,,這就需要獲取函數(shù)運(yùn)行時(shí)的參數(shù)了
方法:用inspect模塊的getcallargs方法去獲取原函數(shù)的參數(shù)
返回的是一個(gè)字典,根據(jù)字典的key去獲取參數(shù)的值
from functools import wraps
from inspect import getcallargs
class Logger(object):
def __init__(self,filename=''):
if filename!='':
self._filename=filename
else:
self._filename="log/errorlog"
def __call__(self,fn):
@wraps(fn)
def wrapper(*args,**kwargs):
func_args=getcallargs(fn,*args,**kwargs)
if 'param1' in func_args.keys():
param1=func_args['param1']
if 'param2' in func_args.keys():
param2=func_args['param2']
with open(self._filename,'a') as logfile_handle:
logfile_handle.write(param1+'/'+param2+'\t finished\n')
logfile_handle.flush()
result=fn(*args,**kwargs)
return result
return wrapper
@Logger(filename='testlog')
def test(param1,param2,param3,param4,param5):
print('Ok,finished')
return param5
if __name__=="__main__":
print(test('1','2','3','4','5'))
print(test('s','g','r','e','b'))
運(yùn)行結(jié)果:

打開(kāi)日志文件:

我們拿到了函數(shù)的第一個(gè)參數(shù)跟第二個(gè)參數(shù)的值并保存到文件中
一些裝飾器的例子(日志那個(gè)請(qǐng)看上文)
from functools import wraps
def memo(fn):
cache = {}
miss = object()
@wraps(fn)
def wrapper(*args):
result = cache.get(args, miss)
if result is miss:
result = fn(*args)
cache[args] = result
return result
return wrapper
@memo
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
這是斐波拉契數(shù)例的遞歸算法。
因?yàn)閒ib(1) fib(0)是重復(fù)計(jì)算的值,將其利用裝飾器預(yù)先緩存(先計(jì)算好fib(1),fib(0)),調(diào)用的時(shí)候直接返回字典的值,達(dá)到優(yōu)化fib(n)的思路
很經(jīng)典
當(dāng)我讀懂這段代碼的時(shí)候,
我內(nèi)心大喊一聲:WO CAO! 還能這樣玩!
2.web后端通過(guò)URL的路由來(lái)調(diào)用相關(guān)注冊(cè)
class MyApp():
def __init__(self):
self.func_map = {}
def register(self, name):
def func_wrapper(func):
self.func_map[name] = func
return func
return func_wrapper
def call_method(self, name=None):
func = self.func_map.get(name, None)
if func is None:
raise Exception("No function registered against - " + str(name))
return func()
app = MyApp()
@app.register('/')
def main_page_func():
return "This is the main page."
@app.register('/next_page')
def next_page_func():
return "This is the next page."
print app.call_method('/')
print app.call_method('/next_page')
注:decorator類中沒(méi)有call(),但是wrapper返回了原函數(shù)。所以,原函數(shù)沒(méi)有發(fā)生任何變化。
這例子只是用來(lái)注冊(cè)u(píng)rl 方法并調(diào)用
防止web訪問(wèn)位置url~
無(wú)返回值的異步多線程調(diào)用(涉及數(shù)據(jù)變化請(qǐng)用鎖)
from threading import Thread
from functools import wraps
def async(func):
@wraps(func)
def async_func(*args, **kwargs):
func_hl = Thread(target = func, args = args, kwargs = kwargs)
func_hl.start()
return func_hl
return async_func
if __name__ == '__main__':
from time import sleep
@async
def print_somedata():
print 'starting print_somedata'
sleep(2)
print 'print_somedata: 2 sec passed'
sleep(2)
print 'print_somedata: 2 sec passed'
sleep(2)
print 'finished print_somedata'
def main():
print_somedata()
print 'back in main'
print_somedata()
print 'back in main'
main()