装饰器 Decorator 可以在不修改函数本体的情况下,为函数添加额外功能。例如,在授权认证的时候,如果没有 Decorator,那么我们可能需要手动写一个函数 auth() 然后在每次目标函数执行之前,先调用一次 auth()、处理异常……然后再执行函数本体。这无疑是非常麻烦的。有了 Decorator 之后,我们可以把 auth() 的调用、异常处理放在 Decorator 中,这样只需要在函数外面写上 @auth 即可检查授权。这样做既可以保持函数内部含义清晰,Decorator 的代码也可以复用。

Decorator 运行逻辑

当 Python 检查到有函数 func() 被装饰器 @dec 装饰后,Python 会在函数被执行前,运行 decorator 的定义代码,用户定义的 func() 实际替换为 wrapper() 的函数体,但是函数名仍然保留. 用户调用的时候还是 func(),但是每次调用还会运行定义在 wrapper() 里内容.

下面这个例子可以帮助我们更好地理解 Decorator 的机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def decorator(some):
print(f"in decorator(some={some}), before decorator(func)")
def decorator(func):
print("in decorator(func), before wrapper()")
def wrapper(*args, **kwargs):
print("in wrapper(), before calling func()")
result = func(*args, **kwargs)
print("in wrapper(), after calling func()")
return result
print("in decorator(func), after wrapper()")
return wrapper
print(f"in decorator(some={some}), after decorator(func)")
return decorator

@decorator(some="$$$")
def myfunc():
print("in myfunc()")

myfunc()
myfunc()

把两行 myfunc() 注释前后观察输出. 即使没有真正调用 myfunc(),decorator 的代码仍然会运行;注释掉两行的 myfunc() 后,decorator 的四个 print() 也只运行了一次,说明并不是每次调用 myfunc() 才展开 decorator 的,而 in wrapper() 的输出随着 myfunc() 调用次数的增多也在增多

定义 Decorator

Decorator 的本质是一个函数,其返回值也是一个函数.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def decorator(function):
def wrapper(*args, **kwargs):
# New Feature Here
# ...... e.g. counter state update
result = function(*args, **kwargs) # 执行原函数
# New Feature Here
# ...... e.g. operator on results
return result

return wrapper


@decorator
def my_function():
print()

Decorator 自己也可以带参数,方法是在定义时多套一层 def,并且第二层同名

1
2
3
4
5
6
7
8
9
10
11
12
13
def decorator_with_arguments(some_args):
def decorator_with_arguments(function):
def wrapper(*args, **kwargs):
# ......
result = function(*args, **kwargs)
# ......
return result
return wrapper
return decorator_with_arguments

@decorator_with_arguments("some args")
def my_function():
print("my_function")

Decorator 保留原函数的 DocString

Decorator 实际返回的函数对象是 wrapper() 而非 func(),因此,decorator 装饰后会把 func() 的 docstring、函数名之类的统统调包为 wrapper(). 想要保留的话,需要使用用 @functools.wraps(func) 来装饰 wrapper()

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


def decorator_with_arguments(some_args):
def decorator_with_arguments(function):
@functools.wraps(function)
def wrapper(*args, **kwargs):
# ......
result = function(*args, **kwargs)
# ......
return result
return wrapper
return decorator_with_arguments

@decorator_with_arguments("some args")
def my_function():
print("my_function")