1、装饰器本质上是一个函数,其左右是在不改变原有代码的基础上扩展功能。装饰器符合开闭原则,即原函数不能变,调用不能变,对扩展开发,对修改封闭
2、装饰器的原理
def f1(x): # 定义函数,计算参数的平方值
return x*x
def func_new(func): # 定义一个装饰器,作用是在f1函数输出结果的前一行加上“正在计算”
def fn(j):
print('正在计算')
return func(j)
return fn
y = func_new(f1)
print(y(10))
整个代码的运行逻辑是:
- 定义了函数 f1 和 func_new,并用函数名 y 接收了 func_new 函数的返回值
- 执行 func_new,将参数 f1 传入,在执行途中定义了函数 fn,并返回函数 fn
- 走到下一行,在 func_new 函数中,发现 fn 已被定义,并在最后一行中给 fn 赋值10(即j=10),开始执行 fn
- 输出“正在计算”,之后返回 func(10)。由于之前已经将 f1 赋值给func函数,故将计算10的平方,后将上面两条输出返回
- 返回的值被 y 接收,最终打印出我们所需的两行结果
正在计算
100
在上面的代码中,我们可以将接收返回值的 y 改成 f1,这么一来,在未来调用函数 f1 时,将会同时出现上面两条结果,达到了装饰器的目的
3、装饰器的写法
def decorator(func): #定义一个装饰器,作用是在所装饰函数的输出前一行加上10个横杠
def inner():
print('-'*10)
func()
return inner
@decorator #在这里用 @装饰器名 即可调用装饰器
def show():
print('abc')
show()
在上面的代码中,用 @decorator 即可代表 show=decorator(show),show 指向 inner
输出的结果如下。往后当每次调用 show() 时,都会输出下面的结果
----------
abc
4、装饰带有参数的函数
def deco(func):
def inner(x):
print('-'*10)
func(x)
return inner
@deco
def show(x):
print(x)
show('abc')
与前一个例子的区别在于,本例在设置装饰器时需要传入参数x(此处的x其实可以用其他参数名代替),与所装饰函数的参数进行对应
5、通用的装饰器,即装饰器在使用时不受所装饰函数是否有参数、参数是什么类型的影响
def deco(func): #在所装饰的函数输出前一行加上十个横杠
def inner(*args,**kwargs):
#使用不定长位置参数、不定长关键字参数,使得装饰器能够接收所装饰函数的任何参数
print('-'*10)
return func(*args,**kwargs)
return inner
@deco
def show(num1,num2):
result = num1 + num2
print(result)
return result
show(1,2)
6、带有参数的装饰器
def deco2(char): #根据参数char的值,在所修饰函数输出结果的前一行加上十个char
def deco1(func):
def inner(*args,**kwargs):
print(char*10)
return func(*args,**kwargs)
return inner
return deco1
@deco2('-') #此处记得给装饰器传参
def show():
print('abc')
show()
7、函数使用多个装饰器
def deco1(func): #在所装饰的函数输出前一行加上十个横杠
def inner1():
print('-'*10)
return func()
return inner1
def deco2(func): #在所装饰的函数输出前一行加上十个星号
def inner2():
print('*'*10)
return func()
return inner2
@deco1
@deco2
def show():
print('abc')
show()
show函数使用了两个装饰器,输出的结果如下。
----------
**********
abc
在这里我们需要注意装饰的调用顺序。在这个例子中,是先装饰deco2,再装饰deco1。整个代码的运行逻辑如下:
- 代码中定义了两个装饰器和一个函数,并在最后调用函数
- 在装饰顺序中,先调用离函数最近的装饰器,故开始调用deco2,即show=deco2(show),此时show指向deco2的内部函数inner2,执行完毕后返回inner2
- 之后执行deco1。此时show已经被inner2赋值,故将inner2传入deco1,即inner2=deco1(inner2),inner2(或者说,show)指向inner1,执行完毕后返回inner1
- 此时show指向inner1,在执行show的时候,从deco1开始执行。首先打印10个横杠,由于inner1内的func已经被inner2赋值,故往下走后直接转至inner2执行
- 在inner2内,先打印10个星号,由于inner2内的func被show赋值,故执行show(),打印“abc”
- 至此整个流程结束
可以通俗理解为,函数是人的身体,离函数最近的装饰器是内衣,之后的装饰器是外套。穿衣服(调用装饰器)时,是先穿内衣后穿外套;脱衣服(执行装饰器)时,是先脱外套再脱内衣
8、解除装饰器的作用
之前曾提到,用装饰器装饰函数后,往后当调用函数时,输出的结果将会加上装饰器的效果;同时如想看到原函数的文档说明,在使用装饰器后,通过原函数名只能查看到装饰器的文档说明。
这些都表明,在使用装饰器后,对原函数的引用做了修改。如果想解除装饰器对该函数的作用,可以导入functools模块中的wraps方法进行解决
from functools import wraps
def decorator(func):
@wraps(func)
def inner():
'''inner'''
print('-'*10)
func()
return inner
@decorator
def show():
'''show'''
print('abc')
show.__wrapped__() #输出不带装饰器效果的原函数的值
print(show.__doc__) #输出原函数的文本说明