装饰器简述
要理解装饰器需要知道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__()
:让类对象可调用,就像函数调用一样,在调用被装饰函数时被调用