如何理解python中的装饰器

前言

写过一段时间的python代码或者阅读过python程序的人一定不会陌生“装饰器”,其语法糖为@。今天就来好好看看到底什么是python装饰器,它到底装饰了什么?以及为什么要使用装饰器。

“装饰器”

什么是装饰器

python装饰器是的本质也是函数或者类,它接受另外的函数的作为其逻辑参数,将真正的业务函数包裹在其中。在不影响业务函数执行情况下,对业务函数添加额外的功能。

为什么要用装饰器

在前面已经说过,装饰器的作用是在不影响业务函数执行情况下, 对业务函数添加额外的功能。例如,需要打印函数的执行日志,一种方法是在业务函数中添加打印日志语句;但是,当函数数量很多时,这种方法就显得不那么靠谱,而且程序中会充斥着大量的重复代码,影响程序的可读性。这时,装饰器就能够发挥很大的作用。

详解装饰器

装饰器总的来说分为函数装饰和类装饰器,函数装饰器中又有无参数装饰器和带参数装饰器,所装饰函数也包括无参函数,一个参数或者多个参数函数,甚至是参数个数不固定的函数,以及带有关键字参数的函数。虽然这里说明了装饰的种类比较多,但是其思想完全一样。所以弄懂装饰器工作原理,这些问题也就迎刃而解了。

无参函数的装饰器

无参函数的装饰器也即是所装饰函数不带有参数,具体看下面代码

1
2
3
4
5
6
7
8
9
10
11
12
# 无参数函数的装饰器
def record_logging(func):
def wrapper():
print('%s is running' % func.__name__)
return func()
return wrapper

def func():
print('hello, 我是一个业务函数')

func = record_logging(func)
func()

执行结果:

1
2
func is running...
hello,我是一个业务函数

上面的代码实现了一个简单的装饰器,在业务函数执行之前打印正在即将执行函数名称。可以看到真正的业务函数是func(),其被包裹在函数wrapper之中。函数record_logging即是装饰器,它的返回值是一个函数wrapper,然后语句func = record_logging(func)在调用装饰器。这样就能够达到我们所需要的目的。在python中,使用@来告诉python,该函数需要被装饰器所装饰,这样就可以省略掉赋值语句func = record_logging(func),具体如下

1
2
3
4
5
6
7
8
9
10
11
def record_logging(func):
def wrapper():
print('%s is running' % func.__name__)
return func()
return wrapper

@record_logging
def func():
print('this function is func()')

func()

执行结果:

1
2
func is running...
hello,我是一个业务函数

可以看出,其执行结果与赋值调用装饰器结果一样。而且使用@使代码更加简洁美观易读,也更加的pythonic。

一个参数函数的装饰器

当所装饰的业务函数带有逻辑参数时,又该如何实现装饰器呢?

1
2
3
4
5
6
7
8
9
10
11
12
# 一个参数函数的装饰器
def record_logging(func):
def wrapper(name):
print('%s is running...' % func.__name__)
return func(name)
return wrapper

@record_logging
def func(name):
print("Printing parameter '{}' is my task".format(name))

func('Chai')

执行结果:

1
2
func is running...
Printing parameter 'Love' is my task

其逻辑思想与无参数函数装饰器一致,其区别仅仅在于wrapper函数接受业务函数参数(仔细对比无参数函数装饰器的写法)。

多个参数甚至不固定参数个数函数的装饰器

这个时候我们就需要用到*args来代替业务函数的逻辑参数。具体使用如下:

1
2
3
4
5
6
7
8
9
10
11
def record_logging(func):
def wrapper(*args):
print('%s is running...' % func.__name__)
return func(*args)
return wrapper

@record_logging
def func(name, age):
print("my name is {0}, and my age is {1}".format(name, age))

func('CL', 22)

执行结果:

1
2
func is running...
my name is CL, and my age is 22

当业务函数参数较多或不固定时,只需要将函数wrapper的参数设置为*args即可。当业务函数中包含关键字参数如None时,设置函数wrapper接收**kwargs即可。

装饰器带有参数

带有参数的装饰器灵活度更高,其相当于与将装饰器再封装了成函数。例如,在查询函数执行日志时,不同的ID有不同的查询权限,具体如下的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#带参数的装饰器
def record_loggings(ID):
def decorator(func):
def wrapper(*args, **kwargs):
if ID==0:
print('权限太低,没法儿查询')
elif ID==1:
print('请稍后,正在查询...')
return func(*args, **kwargs)
return wrapper
return decorator

@record_loggings(ID=0)
def func():
print("虽然我是一个没有参数的业务函数,但是我还是占用了*args,**kwargs哦。")

func()

执行结果:

1
2
权限太低,没法儿查询
虽然我是一个没有参数的业务函数,但是我还是占用了*args,**kwargs哦。

函数装饰器就介绍完了。前面我们说过还有一种装饰器是类装饰器,它是通过类的__call__函数实现的,其具体见如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Decorator(object):
def __init__(self, func):
self.func = func

def __call__(self, name, age):
print('A class decorator is running...')
self.func(name, age)
print('The class decorator ended...')

@Decorator
def func(name, age):
print('my name is {0}, and my age is {1}'.format(name, age))

func("CL", 22)

执行结果:

1
2
3
A class decorator is running...
my name is CL, and my age is 22
The class decorator ended...

这是一个简单的类装饰器,可以看到装饰器的本质还是体现在函数__call__上面(包括参数的传递等),可仔细体会其与函数装饰器之间的异同。

python中内置装饰器

在python中常见的内置装饰器有

  • @property 把方法变成属性调用
  • @staticmethod 静态方法,用来修饰类中的方法,可以使类方法像字段属性一样调用。
  • @classmethod 类方法,与@staticmethod类似,@classmethod需要将class传入被修饰的方法中。

具体的使用方法可以查看官方文档。

总结

本文仔细梳理了python中的装饰器工作原理,以及不同装饰器的实现方法,简单介绍了类装饰器。本文作为python装饰器学习之后的一个笔记,记录在此,以备后查!