本质上,装饰器(decorator)就是一个返回函数的高阶函数。当我们想对已有程序进行功能扩展时,我们往往不会选择修改函数或者模块内部的源代码,这样是不科学也是不现实的(可以看一下开闭原则)。装饰器由此而来,它满足了以下3点原则:
装饰器经常被用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等。
上面对装饰器简单介绍了一下,接下来我们来看看装饰器具体长什么样,以及怎么去装饰函数。
先看一个简单的装饰器:
# 装饰器函数, 接受函数名作为参数
def decorator(func):
def wrapper():
# 提示:wrapper函数中的print()操作只是最简单的示例,根据不同的需求可以换成更有实际意义的操作。
print('before doing something')
func() # 在包装函数中调用被装饰函数
print('something finished')
return wrapper
@decorator
def something():
print('doing something')
print(something.__name__)
something()
'''输出:
wrapper
before doing something
doing something
something finished
'''
看完代码,我们来说明一下装饰器是怎么装饰函数的:
# 方式一: 使用语法糖,在被修饰函数定义前加上 @ + 装饰器的名字
@decorator
def func():
pass
# 方式二: 手动将被修饰函数的名字作为参数传入装饰器中
func = decorator(func)
这两种方式的实际效果是一样的,但是方式一明显更为简捷。
要注意的是,因为返回的那个wrapper()函数名就是wrapper,所以我们需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。Python内置的functools.wraps方法就能替我们解决这个问题,只需在定义wrapper()的前面加上@functools.wraps(func)即可。下面的示例程序都将使用wraps方法。
接下来实现一个更实用的装饰器,它可以修饰带参数且有返回值的函数:
import functools
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs): # 使用可变参数,更具扩展性
print('before doing something')
ret = func(*args, **kwargs) # 使用变量ret存放被装饰函数的返回
print('something finished')
return ret # 包装函数再返回ret,实现返回的传递
return wrapper
@decorator
def get_plus(a, b): # 返回两数之和
print('call get_plus')
return a + b
@decorator
def get_sum(*args, **kwargs): # 返回n个数的和
print('call get_sum')
return sum(args)
print(get_plus.__name__)
print(get_sum.__name__)
sum1 = get_plus(1, 3)
sum2 = get_sum(2, 3, 4)
print(sum1, sum2)
'''输出:
get_plus
get_sum
before doing something
call get_plus
something finished
before doing something
call get_sum
something finished
4 9
'''
可以看到,为wrapper函数添加了可变参数后,装饰器能修饰有固定参数的get_plus函数,也能修饰参数不定的get_sum函数,而且在使用了functools.wraps方法后,被修饰函数的__name__等属性也一并复制了过去。
如果针对不同的函数,我们需要装饰器来区别对待,那么可以这样做:
import functools
def decorator(param): # 装饰器接受参数param
def outer_wrapper(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if param == 'task1': # 根据参数param判断当前修饰的是哪个函数
func(*args, **kwargs)
elif param == 'task2':
func(*args, **kwargs)
print(param,"is finished.")
return wrapper
return outer_wrapper
@decorator(param='task1')
def task1():
print("doing task1 right now..")
@decorator(param='task2')
def task2():
print("doing task2 right now..")
print(task1.__name__, task2.__name__)
task1()
task2()
'''输出:
task1 task2
doing task1 right now..
task1 is finished.
doing task2 right now..
task2 is finished.
'''
分析一下给装饰器传参的操作:
@decorator(param = arg) # 1
def func(*args, **kwargs):
pass
# #1处的语句与#2处的语句是等价的
# 为了容易理解,我把#2右边的逐步化简如下:
func = decorator(arg)(func) # 2
= outer_wrapper(func) # 注意以下两句直接执行是会报错的,此处只是为了方便理解
= wrapper(*args, **kwargs)
通过上面的代码我们可以看到,实际上func经过多层的嵌套最终还是被“包”在最里层的wrapper里面,传递给装饰器的参数param只是为了wrapper可以对不同的函数有区别地“包装”,这也算是对装饰器功能的进一步扩展。