python编程之装饰器

什么是装饰器

在Python中,装饰器是一种高级功能,它允许你在不修改已有代码的情况下,动态地修改或增强函数或类的行为。装饰器本质上是一个函数,它接受另一个函数作为参数,并返回一个新的函数,通常也会在内部定义一个函数来实现这个功能。

为什么需要装饰器

我们假设你的程序实现了say_hello()say_goodbye()两个函数

1
2
3
4
5
6
7
8
9
10
11
def say_hello():
print("hello!")


def say_goodbye():
print("hello!") # 此处应打印goodbye


if __name__ == '__main__':
say_hello()
say_goodbye()

假设上述代码中的say_goodbye函数出现了bug,为了之后能更好的维护,现在需要调用方法前记录函数调用名称,以定位出错位置。
在不使用装饰器的情况下我们可以用以下方法实现

1
2
3
4
5
6
7
8
9
10
11
def say_hello():
print("[DEBUG]: enter say_hello()")
print("hello!")

def say_goodbye():
print("[DEBUG]: enter say_goodbye()")
print("hello!")

if __name__ == '__main__':
say_hello()
say_goodbye()

我们在上述的两个函数内部都加上了对应的打印信息,这样的话就修改了函数内容的代码。如果有多个函数的话,那么每个函数的内部都需要去增加对应的打印信息,当不需要时又要逐个去除,这里就显得非常难受。装饰器能很好的解决这个问题。

装饰器的简单实现

在早些时候 (Python Version < 2.4,2004年以前),为一个函数添加额外功能是用纯闭包的方式实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def debug(func):
def wrapper():
print("[DEBUG]: enter {}()".format(func.__name__))
return func()
return wrapper


def say_hello():
print("hello!")


say_hello = debug(say_hello)

say_hello()

上面的debug函数其实已经是一个装饰器了,它对原函数做了包装并返回了另外一个函数,额外添加了一些功能。因为这样写实在不太优雅,在后面版本的Python中支持了@语法糖,下面代码等同于早期的写法。

1
2
3
4
5
6
7
8
9
10
11
12
13
def debug(func):
def wrapper():
print("[DEBUG]: enter {}()".format(func.__name__))
return func()

return wrapper

@debug
def say_hello():
print("hello!")


say_hello()

但是上述的例子中,被装饰器装饰的函数无法携带参数,所以可以在内部函数wrapper中声明参数被装饰函数的参数

1
2
3
4
5
6
7
8
9
10
11
12
13
def debug(func):
def wrapper(something):
print("[DEBUG]: enter {}()".format(func.__name__))
return func(something)

return wrapper

@debug
def say_hello(something):
print("hello {}!".format(something))


say_hello('theone')

上述例子中虽然解决了被装饰函数的参数问题,但是我们的装饰器所装饰的函数可能拥有多个参数,这个时候上面这种方法就显然行不通,这个时候我们需要将内部函数wrapper的参数列表改成不定长参数,这样就可以轻松应对不同参数个数的函数了。

1
2
3
4
5
6
7
8
9
10
11
12
13
def debug(func):
def wrapper(*args, **kwargs):
print("[DEBUG]: enter {}()".format(func.__name__))
return func(*args, **kwargs)
return wrapper


@debug
def say(something):
print("hello {}!".format(something))


say('theone')

实现一个带参数的装饰器

假设我们前文的装饰器需要完成的功能不仅仅是能在进入某个函数后打出log信息,而且还需指定log的级别,那么我们的装饰器就要有支持传递参数的功能。

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 logging(level):
def wrapper(func):
def inner_wrapper(*args, **kwargs):
print("[{level}]: enter function {func}()".format(
level=level,
func=func.__name__))
return func(*args, **kwargs)
return inner_wrapper
return wrapper


@logging(level='INFO')
def say(something):
print("say {}!".format(something))


# 如果没有使用@语法,等同于
# say = logging(level='INFO')(say)

@logging(level='DEBUG')
def do(something):
print("do {}...".format(something))


if __name__ == '__main__':
say('hello')
do("my work")

基于类的装饰器

我们通过类的方式实现装饰器我们需要借助python中的魔术方法__call__,该魔术方法可以使类的实例对象像函数一一样被调用。

1
2
3
4
5
6
class Demo:
def __call__(*args, **kwargs):
print('实例方法被执行')

demo = Demo()
demo() # 实例方法被执行

接下来我们通过类的方式来实现装饰器

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

def __call__(self, *args, **kwargs):
print("[DEBUG]: enter function {func}()".format(
func=self.func.__name__))
return self.func(*args, **kwargs)


@Logging
def say(something):
print("say {}!".format(something))


say('木木')

基于类的方式实现一个带参数的装饰器

如果需要通过类形式实现带参数的装饰器,那么会比前面的例子稍微复杂一点。那么在构造函数里接收的就不是一个函数,而是传入的参数。通过类把这些参数保存起来。然后在重载__call__方法是就需要接收一个函数并返回一个函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Logging:
def __init__(self, level='INFO'):
self.level = level

def __call__(self, func): # 接收函数
def wrapper(*args, **kwargs):
print("[{level}]: enter function {func}()".format(
level=self.level,
func=func.__name__))
func(*args, **kwargs)

return wrapper # 返回函数


@Logging(level='INFO')
def say(something):
print("say {}!".format(something))


say('木木')