第一次认识装饰器,其实是在学习 Python 的过程了解到的,后来在 JS 中也了解到有对应的实现。最近公司的 TypeScript 项目中自己也写了一个装饰器。
从不同语言对装饰器的实现,可以看出不同语言之间也在相互借鉴。下面我就用 Python 的代码来介绍装饰器。
# 什么是装饰器
装饰器本质上是一个函数。通过接收原始函数,装饰器可以在原始函数执行添加业务逻辑,使用装饰器的好处可以不污染原始函数。
从上面的图中可以知道,使用装饰器的时候,会将原始函数加上一层装饰器中的代码,装饰器的代码并没有侵入到原始函数中,只是在调用原始函数前后,执行装饰器中的代码。
如果不使用装饰器,则需要在原始函数中加入装饰器中的逻辑,这种做法虽然可以实现与装饰器相同的功能,理解起来要更容易,但是这种写法并不能提高复用性。
了解了装饰器之后,我们来看看如何写一个装饰器。
# 编写装饰器
我们可以用一个打印函数来记录函数执行时间的日志装饰器。
import datetime
def log(func):
def wrapper(*args, **kw):
print('{} function was called in {}'.format(func.__name__, datetime.datetime.now()))
return func(*args, **kw)
return wrapper
def hello():
print('hello decorator')
hello = log(hello)
hello()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
其中 log
函数就是一个装饰器,接受一个原始函数作为参数,最后返回一个 wrapper
函数,wrapper
函数内先执行打印日志,然后才调用原始函数。
hello
函数作为原始函数,使用 print
打印一串字符串。然后通过 log
函数返回的新函数重新赋值给 hello
,这时的 hello
函数就具有了 log
函数中的打印日志的功能。
下面执行一下代码,看看结果是否能达到我们预期的那样。执行完会打印下面两行字符串,这也说明了 hello
函数包含了 log
的功能。
hello function was called in 2019-11-17 20:28:09.672132
hello decorator
2
装饰器的原理大概就是这样,只不过我们这种写法过于冗余,需要先声明 hello
函数,之后又将 log
函数的返回值重新赋值给
hello
。Python 提供了语法糖,使用 @装饰器
代替上面的写法。当然还修复了函数的 __name__
属性等,这里就不做展开了。
下面用语法糖体验一下装饰器。
import datetime
def log(func):
def wrapper(*args, **kw):
print('{} function was called in {}'.format(func.__name__, datetime.datetime.now()))
return func(*args, **kw)
return wrapper
@log
def hello():
print('hello decorator')
hello()
2
3
4
5
6
7
8
9
10
11
12
13
14
使用 @装饰器
之后代码更加简洁优雅。如果装饰器需要传递参数,则需要在装饰器添加多一层函数嵌套,通过最外层的函数获取参数。代码如下:
import datetime
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print(text)
print('{} function was called in {}'.format(func.__name__, datetime.datetime.now()))
return func(*args, **kw)
return wrapper
return decorator
@log('test')
def hello():
print('hello decorator')
hello()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 装饰器的用处
我当时学完装饰器之后的第一个想法是可以用来做鉴权。如果有些函数需要用户登陆后才能操作,那么我们可以写一个这样的装饰器,在装饰器内获取用户是否登录,如果登录则调用原始函数;未登录则提示用户进行登录。
如果不使用装饰器的做法,我们则需要在每个函数中编写是否登录的判断,这样不仅有大量重复的代码,而且代码变得不易维护。
当然装饰器的用法不仅于此,像 flask
框架则用装饰器来实现路由管理等等。
不同语言的装饰器只是在写法上略有区别,原理都是一样的。只要懂得原理,完全不用担心写法的差异。
关于装饰器的更多详细的内容就不做展开了,大家有兴趣可以到网络上查阅资料。
# 题目
最后留一道题目给大家分析一下
def dec_a(func):
print('111')
def wrapper(*args, **kw):
print('222')
func(*args, **kw)
print('333')
return wrapper
def dec_b(func):
print('aaa')
def wrapper(*args, **kw):
print('bbb')
func(*args, **kw)
print('ccc')
return wrapper
@dec_a
@dec_b
def test():
print('test')
test()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
如果文中有说的不对的地方,欢迎留言指出改正。如果你对装饰器有不同的理解,也欢迎留言讨论。
如果还有其他疑问,欢迎大家扫码关注我的公众号【前端develop】给我留言。