python装饰器的理解与使用

摘要:本篇主要描述什么是python的装饰器,以及装饰器是如何使用和工作原理。

装饰器

装饰器本质上是一个Python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。它经常用于有切面需求的场景,比如:插入日志、性能测试、事务处理、缓存、权限校验等场景。装饰器是解决这类问题的绝佳设计,有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用。概括的讲,装饰器的作用就是为已经存在的对象添加额外的功能。

我们来看个例子:

1
2
def demo1():
print("I'm demo1.")

但现在我需要在调用函数时记录执行日志,于是在代码中添加:

1
2
3
def demo1():
print("I'm demo1.")
logging.info("demo1 is running.")

但是如果其他函数也需要的话一个一个添加就会很麻烦。

接下来我们可以写一个函数来代替一个个添加。

1
2
3
4
5
6
7
8
def use_logging(func):
logging.info("%s is running."%func.__name__)
func()

def demo1():
print("I'm demo1.")

use_logging(demo1)

逻辑上不难理解, 但是这样的话,我们每次都要将一个函数作为参数传递给use_logging函数。而且这种方式已经破坏了原有的代码逻辑结构,之前执行业务逻辑时,执行运行demo1(),但是现在不得不改成use_logging(demo1)。那么有没有更好的方式的呢?当然有,答案就是装饰器。

简单装饰器

先看这个新的例子来解释闭包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# print_msg是外围函数
def print_msg():
msg = "I'm closure"

# printer是嵌套函数
def printer():
print(msg)

return printer


# 这里获得的就是一个闭包
closure = print_msg()
# 输出 I'm closure
closure()

msg是一个局部变量,在print_msg函数执行之后应该就不会存在了。但是嵌套函数引用了这个变量,将这个局部变量封闭在了嵌套函数中,这样就形成了一个闭包。

结合这个例子再看维基百科的解释,就清晰明了多了。闭包就是引用了自有变量的函数,这个函数保存了执行的上下文,可以脱离原本的作用域独立存在。

下面看看python中的装饰器:

1
2
3
4
5
6
7
8
9
import functools

def log(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
print("call %s():"%func.__name__)
print("args = {}".format(*args))
return func(*args,**kwargs)
return wrapper()

调用它:

1
2
3
4
5
@log
def test(p):
print(test.__name__ + "param:"+p)

test("I'm param")

输出:

image-20200621230457462

装饰器在调用是时候使用了@语法,其实实际上的调用如下:

1
2
3
4
def test(p):
print(test.__name__ + "param:"+p)
wrapper = log(test)
wrapper ("I'm param")

@语法只是将函数传入装饰器函数,并无神奇之处。

值得注意的是@functools.wraps(func),这是python提供的装饰器。它能把原函数的元信息拷贝到装饰器里面的 func 函数中。函数的元信息包括docstring、name、参数列表等等。可以尝试去除@functools.wraps(func),你会发现test.__name__的输出变成了wrapper。

带参数的装饰器

装饰器允许传入参数,一个携带了参数的装饰器将有三层函数,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import functools

def log_with_param(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print('call %s():' % func.__name__)
print('log_param = {}'.format(text))
return func(*args, **kwargs)

return wrapper

return decorator

@log_with_param("param")
def test_with_param():
print(test_with_param.__name__)
test_with_param()

看到这个代码是不是又有些疑问,内层的decorator函数的参数func是怎么传进去的?和上面一般的装饰器不大一样啊。

其实道理是一样的,将其@语法去除,恢复函数调用的形式一看就明白了:

1
2
3
4
5
6
# 传入装饰器的参数,并接收返回的decorator函数
decorator = log_with_param("param")
# 传入test_with_param函数
wrapper = decorator(test_with_param)
# 调用装饰器函数
wrapper("I'm a param")

输出结果与正常使用装饰器相同:

image-20200621232456603

至此,装饰器这个有点费解的特性也没什么神秘了。

装饰器这一语法体现了Python中函数是第一公民,函数是对象、是变量,可以作为参数、可以是返回值,非常的灵活与强大。

Python中引入了很多函数式编程的特性,需要好好学习与体会。

------- 本文结束  感谢您的阅读 -------