有了上一篇装饰器原理的简单介绍,那么可以来探索下装饰器的一些高级用法

装饰器之装饰有参数的函数

简单版本的装饰有参数函数

上一篇中提到的语法糖装饰器语法是 @函数名 这种格式,相当与 被装饰的函数名 = 函数名(被装饰的函数名) 这样的情况,代码例子如下:

1
2
3
4
5
6
7
8
def decorator(func):
def wrapper():
func()
return wrapper

@decorator
def test():
print("test")

上面@decorator 也就相当于 test = decorator(test),那么,如果test函数带有参数的情况下,改怎么装饰呢?
如果我们不对原有装饰器进行改变,还是用原来的来装饰一个有参数的函数的话,python解释器会提示缺少参数错误。如下代码的例子:

1
2
3
4
5
6
7
8
9
10
def decorator(func):
def wrapper():
func()
return wrapper

@decorator
def test(arg):
print("test",arg)

test("helloworld")

运行结果:

1
2
3
4
Traceback (most recent call last):
File "demo6.py", line 10, in <module>
test("helloworld")
TypeError: wrapper() takes 0 positional arguments but 1 was given

提示我们wrapper函数调用缺少参数,那么我们不妨从 test = decorator(test) 这行代码的内部运行流程来理解下为啥会出现这样的错误,当 decorator(test) 调用时,返回内部定义的wrapper函数变量,而test是带有参数的,用来引用一个没有参数的wrapper,就报错了。明白了这一点,那么接下来我们就好办,修改代码如下:

1
2
3
4
5
6
7
8
9
10
def decorator(func):
def wrapper(arg):
func(arg)
return wrapper

@decorator
def test(arg):
print("test",arg)

test("helloworld")

运行结果:

1
test helloworld

没有任何的问题,好了,这只是一种简单版本的带参数函数装饰器,接下来再看看通用版本的。

通用版本的装饰有参数函数装饰器

考虑到一个装饰器的通用性,实际开发过程中可能不仅要装饰一个参数的函数,也可能装饰带有多个参数的函数,就比如要在原有的函数之上添加日志记录功能,原有函数有很多,参数也不尽相同,那么就要写一个通用版本的装饰器。相信大家都学过python的可变长参数这个知识点,函数参数列表中参数名前面加上*,代表传递多个除字典以外普通类型的参数,写上**的话,代表传递多个字典参数,有了这个知识点的基础,那么实现一个通用版本装饰器就很简单,修改上面代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def decorator(func):
def wrapper(*args,**kwargs):
func(*args,*kwargs)
return wrapper

@decorator
def test1(arg):
print("test",arg)

@decorator
def test2(arg1,arg2,arg3):
print("test",arg1,arg2,arg3)

test1("helloworld")

test2("hello","world","python")

运行结果:

1
2
test helloworld
test hello world python

好了,上面这个装饰器则能满足装饰任何参数个数的函数

装饰带有返回值的函数

平时开发过程中,有返回值的函数非常之多,有时候我们要依靠返回值来判断函数执行是否达到了我们的预期效果,那么如何来装饰一个有返回值的函数呢?
就比如下面这段代码,我们来装饰一个有返回值的函数

1
2
3
4
5
6
7
8
9
10
def makeBold(func):
def wrapper(*args,**kwargs):
func(*args,**kwargs)
return wrapper

@makeBold
def get_str(info):
return info

print(get_str("hello world"))

运行结果:

1
None

咦?我们的get_str函数明明有返回值,装饰过后为啥没了呢?原因很简单,因为在内部wrapper函数中,并没有任何的返回值,它仅仅只是调用了一下get_str函数。那么我们不妨对调用函数的结果进行返回,看看情况会怎么样?

1
2
3
4
5
6
7
8
9
10
def makeBold(func):
def wrapper(*args,**kwargs):
return "<b>" + func(*args,**kwargs) + "</b>"
return wrapper

@makeBold
def get_str(info):
return info

print(get_str("hello world"))

运行结果:

1
<b>hello world</b>

结果在我们的期望之中,那么装饰带有返回值的函数,可以这么来做。

多个装饰器装饰一个函数

这个知识点的话先讲原理不太好,我们先从代码的运行结果来看,再进行分析多个装饰器装饰一个函数是怎么样的一个过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def add_verity1(func):
print("装饰验证1功能")
def wrapper(*args,**kwargs):
print("---验证1功能执行---")
return func(*args,**kwargs)
return wrapper


def add_verity2(func):
print("装饰验证2功能")
def wrapper(*args,**kwargs):
print("---验证2功能执行---")
return func(*args,**kwargs)
return wrapper

@add_verity1
@add_verity2
def test():
print("---test---")

test()

运行结果:

1
2
3
4
5
装饰验证2功能
装饰验证1功能
---验证1功能执行---
---验证2功能执行---
---test---

从运行结果来看,显示装饰了下面verity2的功能,再装饰verity1,函数调用时候是先运行验证1,再运行验证2,最后调用之前函数本身。其实这也不难理解,先装饰verity2,也就是test = add_verity2(test),此时的test已经变成了引用add_verity2内部的wrapper函数,那么自然也就带上了验证2的功能,然后再装饰verity1,test = add_verity1(test),此时带上了验证2功能的test再来引用add_verity1内部的wrapper函数。

装饰器带参数

如果一个装饰器要可以根据程序员传递的参数来进行装饰不同的功能,可以对装饰器也增加参数,这一点稍微有点复杂,且看代码再来理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def add_verity(verity_type):
if verity_type == "验证1":
def outer_wrapper(func):
def inner_wrapper(*args,**kwargs):
print("---验证1功能执行---")
return func(*args,**kwargs)
return inner_wrapper
return outer_wrapper
elif verity_type == "验证2":
def outer_wrapper(func):
def inner_wrapper(*args,**kwargs):
print("---验证2功能执行---")
return func(*args,**kwargs)
return inner_wrapper
return outer_wrapper

@add_verity(verity_type="验证1")
def test1():
print("---test1---")


@add_verity(verity_type="验证2")
def test2():
print("---test2---")

test1()
test2()

运行结果:

1
2
3
4
---验证1功能执行---
---test1---
---验证2功能执行---
---test2---

这样子我们就可以对装饰器传递参数来选择装饰功能,那么上面这一装饰器装饰过程又是怎么样的呢?

1
2
3
4
1.@add_verity(verity_type="验证1"),这行代码,首先是对add_verity(verity_type="验证1")的调用
2.调用完毕,内部根据传递的参数,运行不同的if分之,假如参数是"验证1",也就会把验证1分之内部的outer_wrapper引用返回回去,此时也就相当于 @验证1分之下的outer_wrapper
3.这样子接下来的就好理解了,test1 = outer_wrapper(test1),也就是test1引用了验证1分之内部的inner_wrapper函数
test2装饰的原理同上

呼~终于熬夜把装饰器的知识点写完了