函数装饰器
# 函数装饰器
函数装饰器(Function Decorators):从字面上理解,就是装饰一个函数。可以在不修改原代码的情况下,为被装饰的函数添加一些功能并返回它。
函数装饰器的语法是将 @装饰器名
放在被装饰函数上面,下面是个例子:
@dec
def func():
pass
2
3
# 前置概念
首先需要明确以下几个概念和原则,才能更好的理解装饰器:
- Python 程序是从上往下顺序执行的,碰到函数的定义代码块不会立即执行,只有等到该函数被调用时,才会执行其内部的代码块。
- Python 中函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
- 可以将一个函数作为参数传递给另一个函数。
有了这些基本的概念,我们就可以通过一个实例来讲解 Python 中函数装饰器的用法了。
# 模拟场景
模拟一个场景,假设在某项目中,有下列五个接口(f1~f5):
def f1():
print("第一个接口......")
def f2():
print("第二个接口......")
def f3():
print("第三个接口......")
def f4():
print("第四个接口......")
def f5():
print("第五个接口......")
2
3
4
5
6
7
8
9
10
现在有个需求,每个接口执行完后需要记录日志。如果逐次修改这五个接口的内部代码显然是一种比较糟糕的方案,我们可以使用装饰器完成这一任务。代码如下:
def outer(func):
def inner():
result = func()
print("日志添加成功")
return result
return inner
@outer
def f1():
print("第一个接口......")
@outer
def f2():
print("第二个接口......")
@outer
def f3():
print("第三个接口......")
@outer
def f4():
print("第四个接口......")
@outer
def f5():
print("第五个接口......")
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
使用装饰器 @outer
,仅需对原接口代码进行拓展,就可以实现操作结束后保存日志,并且无需对原接口代码做任何修改,调用方式也不用变。
# 实现原理
下面以 f1
函数为例,对函数装饰器的实现原理进行分析:
def outer(func):
def inner():
result = func()
print("日志添加成功")
return result
return inner
@outer
def f1():
print("第一个接口......")
2
3
4
5
6
7
8
9
10
Step1:程序开始运行,解释器从上往下逐行解释并运行代码,读到
def outer(func):
的时候,把函数体加载到内存里,然后过。Step2:读到
@outer
的时候,程序发现这是个装饰器,按规则要立即执行它,于是程序开始运行@
后面那个名为outer
的函数。(@outer
相当于outer(f1)
)Step3:程序正式进入到
outer
函数,开始执行装饰器的语法规则。规则是:- 被装饰的函数(整个函数体)会被当作参数传递给装饰函数。
- 装饰函数执行它自己内部的代码后,会将它的返回值赋值给被装饰的函数。
- 此处:原来的
f1
函数被当做参数(func
)传递给了outer
,而f1
这个函数名之后会指向inner
函数。
(装饰前后函数名 f1 指向)
Step4:程序开始执行
outer
函数内部的内容,一开始它又碰到了一个函数inner
,inner
函数定义块被程序观察到后不会立刻执行,而是读入内存中(这是默认规则)。Step5:再往下,碰到
return inner
,返回值是个函数名,并且这个函数名会被赋值给f1
这个被装饰的函数,也就是f1 = inner
。换句话说:此时f1
函数被新的函数inner
覆盖了。Step6:接下来,当调用方依然通过
f1()
的方式调用f1
函数时,执行的就不再是旧的f1
函数的代码,而是inner
函数的代码。- 在本例中,它会先执行
func
函数并将返回值赋值给变量result
,这个func
函数就是旧的f1
函数;接着,它会打印日志保存,这只是个示例,可以换成任何你想要的;最后返回result
这个变量给调用方。
- 在本例中,它会先执行
Step7:最后,调用方可以和以前一样通过
res = f1()
的方式接受result
的值。
# 为什么要两层函数
这里可能会有疑问,为什么我们要搞一个 outer
函数一个 inner
函数这么复杂呢?一层函数不行吗?
答:因为 @outer
这句代码在程序执行到这里的时候就会自动执行 outer
函数内部的代码,如果不封装一下,在调用方还未进行调用的时候,就执行了,这和初衷不符。当然,如果你对这个有需求也不是不行。
请看下面的例子,它只有一层函数:
def outer(func):
result = func()
print("日志添加成功")
return result
@outer
def f1():
print("第一个接口......")
2
3
4
5
6
7
8
尝试敲一下上述代码,可以发现我们只是定义好了装饰器,还没有调用 f1
函数呢,程序就把工作全做了。这就是为什么要封装一层函数的原因。
# 函数参数传递
上面的例子中,f1
函数没有参数,在实际情况中肯定会需要参数的,函数的参数怎么传递的呢?看下面一个例子:
def outer(func):
def inner(username):
result = func(username)
print("日志添加成功")
return result
return inner
@outer
def f1(name):
print("{0}正在连接第一个接口......".format(name))
# 调用方法
f1("zhangsan")
2
3
4
5
6
7
8
9
10
11
12
13
在 inner
函数的定义部分也加上一个参数,调用 func
函数(即装饰后的 f1
函数)时传递这个参数就可以了。、
可问题又来了,如果 f2
函数有 2 个参数,f3
函数有 3 个参数,该怎么传递?通过万能参数 *args
和 **kwargs
就可以了。简单修改一下上面的代码:
def outer(func):
def inner(*args, **kwargs):
result = func(*args,**kwargs)
print("日志添加成功")
return result
return inner
@outer
def f2(name, age):
print("{0}正在连接第二个接口......".format(name))
# 调用方法
f2("lisi", 14)
2
3
4
5
6
7
8
9
10
11
12
13
# 多层装饰器
上面已经介绍了函数装饰器的基本概念和用法,接下来再进一步,一个函数可以被多个函数装饰吗?答案是可以的。看下面的例子:
def outer1(func):
def inner(*args, **kwargs):
print("身份认证成功")
result = func(*args, **kwargs)
print("日志添加成功")
return result
return inner
def outer2(func):
def inner(*args, **kwargs):
print("代码开始执行")
result = func(*args, **kwargs)
print("代码执行完毕")
return result
return inner
@outer1
@outer2
def f1(name, age):
print("{0}正在连接第一个接口......".format(name))
# 调用方法
f1("zhangsan", 13)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
怎么分析多装饰器情况下的代码运行顺序呢?可以将它理解成洋葱模型:每个装饰器一层层包裹住最内部核心的原始函数,执行的时候逐层穿透进入最核心内部,执行内部核心函数后,再反向逐层穿回来。
所以,最后的运行结果就显而易见了:
身份认证成功
代码开始执行
zhangsan正在连接第一个接口......
代码执行完毕
日志添加成功
2
3
4
5
# 装饰器携带参数
装饰器自己可以有参数吗?答案也是可以的。看下面的例子:
def say_hello(country):
def wrapper(func):
def deco(*args, **kwargs):
if country == "China":
print("你好")
elif country == "America":
print("Hello")
else:
return
func(*args, **kwargs)
return deco
return wrapper
@say_hello("China")
def f1():
print("我来自中国")
@say_hello("America")
def f2():
print('I am from America')
f1()
f2()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
运行结果:
你好
我来自中国
Hello
I am from America
2
3
4
# 总结
装饰器体现的是设计模式中的装饰模式,实际上,在 Python 中装饰器可以用函数实现,也可以用类实现。我在实际开发中函数装饰器用的比较多,所以本文主要介绍的是函数装饰器。
而如果要对装饰器的用法作更加深入的学习,官方文档和框架源码是比较好的学习对象。
(完)