Python装饰器用一个可调用对象包装另一个函数,从而在不改原函数源码的前提下增加行为,例如打印日志、统计耗时、做权限判断。@装饰器 是语法糖,等价于手工赋值 函数 = 装饰器(函数)。实现上,装饰器通常返回一个内层函数,也就是闭包。
先套一层
def my_decorator(func):
def wrapper():
print("函数执行前")
func()
print("函数执行后")
return wrapper
@my_decorator
def say_hi():
print("Hi!")
say_hi()输出:
函数执行前
Hi!
函数执行后@my_decorator 写在定义上方时,解释器会执行 say_hi = my_decorator(say_hi),之后名字 say_hi 指向的是包装后的函数。
本质:接收函数,返回函数
装饰器给函数穿外套:wrapper 在外层加日志/计时等能力,再调用 func,@ 只是 say_hi = my_decorator(say_hi) 的语法糖。
def my_decorator(func):
def wrapper():
func()
return wrapper
@my_decorator
def f1():
pass
def f2():
pass
f2 = my_decorator(f2)
带 @ 与不带 @ 的两种写法,效果相同。
原函数带参数
包装函数需要把位置参数和关键字参数原样转交,常用 *args / **kwargs,并 return 原函数的返回值:
def log(func):
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__}, 参数={args}")
return func(*args, **kwargs)
return wrapper
@log
def add(a, b):
return a + b
print(add(3, 5))计时示例
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
cost = time.time() - start
print(f"{func.__name__} 耗时 {cost:.4f} 秒")
return result
return wrapper
@timer
def slow_work():
time.sleep(0.1)
return "done"
print(slow_work())保留原函数元信息
直接替换后,__name__、__doc__ 往往会变成 wrapper 的。用 functools.wraps 可以把原函数的元数据拷到包装函数上:
import functools
def my_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@my_decorator
def hello():
"""打个招呼"""
print("hi")
print(hello.__name__)
print(hello.__doc__)常见误区
- 包装函数未声明
*args/**kwargs时,一旦原函数需要参数就会报错。 - 调用原函数后忘记
return,调用方拿到的就是None。 - 不做
wraps时,调试输出和帮助文档容易对不上原函数名。