裝飾器簡(jiǎn)述
要理解裝飾器需要知道Python高階函數(shù)和python閉包,Python高階函數(shù)可以接受函數(shù)作為參數(shù)懈费,也可以返回函數(shù)羹饰,閉包的內(nèi)部函數(shù)可以訪問外部函數(shù)的局部變量孝冒。Python的裝飾器正式基于高階函數(shù)和閉包尊惰。
來看一個(gè)簡(jiǎn)單的裝飾器:
def dec_func(fun):
def wrapper():
print('In Wapper!')
fun()
print('After fun')
return wrapper
@dec_func
def foo():
print('foo')
foo()
In Wapper!
foo
After fun
可以看到裝飾器不影響foo()函數(shù)的正常功能讲竿,它接受一個(gè)函數(shù)作為參數(shù),然后對(duì)這個(gè)函數(shù)進(jìn)行裝飾后返回給原來的函數(shù)名弄屡。
裝飾器的原理類似下面的函數(shù):
def bar():
print('Bar')
bar = dec_func(bar)
bar()
In Wapper!
Bar
After fun
裝飾器的運(yùn)行首先是將函數(shù)帶入裝飾器题禀,原本的函數(shù)名接受裝飾器“裝飾”后的返回函數(shù),再調(diào)用這個(gè)返回函數(shù)琢岩。
裝飾器就是對(duì)函數(shù)進(jìn)行裝飾投剥,“裝飾”以為這并不會(huì)對(duì)函數(shù)的正常運(yùn)行造成影響师脂,僅僅是對(duì)函數(shù)的一些功能進(jìn)行額外的補(bǔ)充担孔。
裝飾器是一個(gè)函數(shù),接受一個(gè)函數(shù)(或者類)作為參數(shù)吃警,返回值也是也是一個(gè)函數(shù)(或者類)
無參裝飾器
無參裝飾器就是不接受參數(shù)的裝飾器糕篇,無參裝飾器嵌套了兩層函數(shù),一個(gè)是外部函數(shù)接受參數(shù)酌心,一個(gè)是內(nèi)部的返回函數(shù)拌消。
import time
def timeit(func):
def wrapper(): # wrapper是返回函數(shù),被裝飾的函數(shù)的函數(shù)名接受這個(gè)函數(shù)安券,相當(dāng)于f = wrapper()
start = time.clock()
func() # func就是被傳入裝飾器的函數(shù)墩崩,func()在wrapper()內(nèi),在調(diào)用wrapper時(shí)侯勉,會(huì)調(diào)用func()
end = time.clock()
print('Used {}'.format(end - start))
return wrapper
@timeit
def f():
print('In f()')
f() # 此時(shí)f = wrapper
In f()
Used 0.00031399999999992545
1.首先鹦筹,把foo函數(shù)當(dāng)做參數(shù)傳入timeit
2.foo接受返回的函數(shù)wrapper,此時(shí)的foo指向wrapper址貌,執(zhí)行foo其實(shí)執(zhí)行wrapper
3.調(diào)用foo其實(shí)調(diào)用了wrapper铐拐,在調(diào)用wrapper時(shí)又重新調(diào)用了原本的foo函數(shù),在調(diào)用wrapper其實(shí)調(diào)用foo
多裝飾器
def deco1(func):
def wrapper():
print('In Deco1')
func()
return wrapper
def deco2(func):
def wrapper():
print('In Deco2')
func()
return wrapper
@deco2
@deco1
def foo():
print('In foo()')
foo()
In Deco2
In Deco1
In foo()
等價(jià)與:
foo = deco2(deco1(foo))
在多裝飾器中练对,緊挨著被裝飾函數(shù)的裝飾器屬于最內(nèi)層遍蟋,最先調(diào)用,最外層的裝飾器最后調(diào)用螟凭。
有參裝飾器
def make_header(level): #接受參數(shù)
print('Create decorator')
def decorator(func): #接受函數(shù)
print('Initialize...')
def wrapper():
print('Call')
return '<h{0}>{1}</h{0}>'.format(level, func())
return wrapper
return decorator
@make_header(2)
def get_content():
return 'hello world'
Create decorator
Initialize...
get_content()
Call
'<h2>hello world</h2>'
等價(jià)于:
get_content = make_header(2)
get_content = decorator(get_content)
def log(prefix): # 接受裝飾器的參數(shù)
def log_decorator(f): # 內(nèi)部定義的wapper負(fù)責(zé)輸出log的參數(shù)+被裝飾函數(shù)名稱虚青, 接受被裝飾函數(shù)
def wrapper(*args, **kw): # 接受被裝飾函數(shù)的參數(shù)
# 將log函數(shù)的參數(shù)引用
print('[%s] in decorate wrapper s%s()...' % (prefix, f.__name__))
f(*args, **kw)
return wrapper # 返回內(nèi)部函數(shù)給裝飾器
return log_decorator # 返回裝飾器給log函數(shù)
@log('DEBUG')
def test():
print('out decorate run test()')
print(test())
[DEBUG] in decorate wrapper stest()...
out decorate run test()
None
有參裝飾器嵌套了三層函數(shù),最外層的函數(shù)接受裝飾器的參數(shù)螺男,中間的函數(shù)接受被裝飾函數(shù)棒厘,最內(nèi)層的函數(shù)接受被裝飾函數(shù)的參數(shù)钟些。
import time
from functools import reduce
def performance(unit):
def perf_decorator(f):
def wrapper(*args, **kargs):
start_time = time.time()
run_func = f(*args, **kargs)
end_time = time.time()
run_time = (end_time - start_time) * 1000 if unit == 'ms' else (end_time - start_time)
print('call %s() in %f %s' % (f.__name__, run_time, unit))
return run_func
return wrapper
return perf_decorator
@performance('ms')
def factorial(n):
return reduce(lambda x,y: x*y, range(1, n+1))
print(factorial(10))
call factorial() in 0.010252 ms
3628800
裝飾器的影響
def log(f):
def wrapper(*args, **kw):
print('call...')
return f(*args, **kw)
return wrapper
@log
def f2(x):
pass
print(f2.__name__)
wrapper
由于decorator返回的新函數(shù)函數(shù)名已經(jīng)不是'f2',而是@log內(nèi)部定義的'wrapper'绊谭。 這對(duì)于那些依賴函數(shù)名的代碼就會(huì)失效政恍。decorator還改變了函數(shù)的__doc__
等其它屬性。
改善:
import functools
def log(f):
@functools.wraps(f)
def wrapper(*args, **kw):
print('call...')
return f(*args, **kw)
return wrapper
最后需要指出达传,由于我們把原函數(shù)簽名改成了(*args, **kw)
篙耗,因此,無法獲得原函數(shù)的原始參數(shù)信息宪赶。 即便我們采用固定參數(shù)來裝飾只有一個(gè)參數(shù)的函數(shù)
def log(f):
@functools.wraps(f)
def wrapper(x):
print('call...')
return f(x)
return wrapper
也可能改變?cè)瘮?shù)的參數(shù)名宗弯,因?yàn)樾潞瘮?shù)的參數(shù)名始終是 'x',原函數(shù)定義的參數(shù)名不一定叫 'x'
import time
import functools
def performance(unit):
def perf_decorator(f):
@functools.wraps(f)
def wrapper(*args, **kw):
t1 = time.time()
r = f(*args, **kw)
t2 = time.time()
t = (t2 - t1) * 1000 if unit == 'ms' else (t2 - t1)
print('call %s() in %f %s' % (f.__name__, t, unit))
return r
return wrapper
return perf_decorator
@performance('ms')
def factorial(n):
return reduce(lambda x, y: x * y, range(1, n + 1))
print(factorial(10))
call factorial() in 0.008821 ms
3628800
print(factorial.__name__)
factorial
基于類的裝飾器
可以定義基于類的裝飾器
class Bold(object):
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
return '<b>' + self.func(*args, **kwargs) + '</b>'
@Bold
def hello(name):
return 'hello %s' % name
hello('world')
'<b>hello world</b>'
-
__init__()
:它接收一個(gè)函數(shù)作為參數(shù)搂妻,也就是被裝飾的函數(shù) -
__call__()
:讓類對(duì)象可調(diào)用蒙保,就像函數(shù)調(diào)用一樣,在調(diào)用被裝飾函數(shù)時(shí)被調(diào)用