裝飾器簡述
要理解裝飾器需要知道Python高階函數和python閉包,Python高階函數可以接受函數作為參數,也可以返回函數,閉包的內部函數可以訪問外部函數的局部變量。Python的裝飾器正式基于高階函數和閉包。
來看一個簡單的裝飾器:
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()函數的正常功能,它接受一個函數作為參數,然后對這個函數進行裝飾后返回給原來的函數名。
裝飾器的原理類似下面的函數:
def bar():
print('Bar')
bar = dec_func(bar)
bar()
In Wapper!
Bar
After fun
裝飾器的運行首先是將函數帶入裝飾器,原本的函數名接受裝飾器“裝飾”后的返回函數,再調用這個返回函數。
裝飾器就是對函數進行裝飾,“裝飾”以為這并不會對函數的正常運行造成影響,僅僅是對函數的一些功能進行額外的補充。
裝飾器是一個函數,接受一個函數(或者類)作為參數,返回值也是也是一個函數(或者類)
無參裝飾器
無參裝飾器就是不接受參數的裝飾器,無參裝飾器嵌套了兩層函數,一個是外部函數接受參數,一個是內部的返回函數。
import time
def timeit(func):
def wrapper(): # wrapper是返回函數,被裝飾的函數的函數名接受這個函數,相當于f = wrapper()
start = time.clock()
func() # func就是被傳入裝飾器的函數,func()在wrapper()內,在調用wrapper時,會調用func()
end = time.clock()
print('Used {}'.format(end - start))
return wrapper
@timeit
def f():
print('In f()')
f() # 此時f = wrapper
In f()
Used 0.00031399999999992545
1.首先,把foo函數當做參數傳入timeit
2.foo接受返回的函數wrapper,此時的foo指向wrapper,執行foo其實執行wrapper
3.調用foo其實調用了wrapper,在調用wrapper時又重新調用了原本的foo函數,在調用wrapper其實調用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()
等價與:
foo = deco2(deco1(foo))
在多裝飾器中,緊挨著被裝飾函數的裝飾器屬于最內層,最先調用,最外層的裝飾器最后調用。
有參裝飾器
def make_header(level): #接受參數
print('Create decorator')
def decorator(func): #接受函數
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>'
等價于:
get_content = make_header(2)
get_content = decorator(get_content)
def log(prefix): # 接受裝飾器的參數
def log_decorator(f): # 內部定義的wapper負責輸出log的參數+被裝飾函數名稱, 接受被裝飾函數
def wrapper(*args, **kw): # 接受被裝飾函數的參數
# 將log函數的參數引用
print('[%s] in decorate wrapper s%s()...' % (prefix, f.__name__))
f(*args, **kw)
return wrapper # 返回內部函數給裝飾器
return log_decorator # 返回裝飾器給log函數
@log('DEBUG')
def test():
print('out decorate run test()')
print(test())
[DEBUG] in decorate wrapper stest()...
out decorate run test()
None
有參裝飾器嵌套了三層函數,最外層的函數接受裝飾器的參數,中間的函數接受被裝飾函數,最內層的函數接受被裝飾函數的參數。
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返回的新函數函數名已經不是'f2',而是@log內部定義的'wrapper'。 這對于那些依賴函數名的代碼就會失效。decorator還改變了函數的__doc__
等其它屬性。
改善:
import functools
def log(f):
@functools.wraps(f)
def wrapper(*args, **kw):
print('call...')
return f(*args, **kw)
return wrapper
最后需要指出,由于我們把原函數簽名改成了(*args, **kw)
,因此,無法獲得原函數的原始參數信息。 即便我們采用固定參數來裝飾只有一個參數的函數
def log(f):
@functools.wraps(f)
def wrapper(x):
print('call...')
return f(x)
return wrapper
也可能改變原函數的參數名,因為新函數的參數名始終是 'x',原函數定義的參數名不一定叫 '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__()
:它接收一個函數作為參數,也就是被裝飾的函數 -
__call__()
:讓類對象可調用,就像函數調用一樣,在調用被裝飾函數時被調用