装饰器本质上是一个 Python 函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能。装饰器的返回值也是一个函数对象。利用 Python 函数作为一等对象的特性,我们可以将函数作为参数传递给另一个函数,并在该函数内部对其进行增强处理。
第一阶段:理解函数作为对象的基础
在编写装饰器之前,必须先理解 Python 中函数也是对象,可以被赋值给变量,也可以作为参数传递。
- 打开 Python 编辑器或交互式终端。
- 定义一个简单的
say_hello函数。
def say_hello():
print("Hello, Python!")
- 创建一个变量
greet,并将say_hello函数赋值给它。注意这里没有使用括号(),因此是将函数本身赋值,而不是调用它。
greet = say_hello
- 调用
greet变量,观察是否执行了原函数。
greet()
输出结果将是 Hello, Python!。这证明了函数名可以像变量一样被传递和引用。
第二阶段:构建闭包结构
闭包是装饰器的核心。闭包是指在一个外部函数中定义了一个内部函数,内部函数引用了外部函数的变量,然后外部函数返回这个内部函数。
- 定义一个外部函数
outer_wrapper,它接收一个函数作为参数(我们暂且称之为original_func)。 - 在外部函数内部,定义一个嵌套的内部函数
inner_wrapper。 - 在内部函数中,编写增强逻辑(例如打印日志),然后 调用 传入的
original_func。 - 让外部函数返回这个内部函数
inner_wrapper。
def outer_wrapper(original_func):
# 内部函数,用于包装原始函数
def inner_wrapper():
print("--- 开始执行 ---")
original_func()
print("--- 执行结束 ---")
# 返回内部函数对象(注意不要加括号)
return inner_wrapper
第三阶段:手动应用装饰器逻辑
现在我们已经有了一个包装器,让我们不使用 @ 语法糖,手动来装饰一个函数。
- 定义一个目标函数
show_message。
def show_message():
print("我是核心业务逻辑。")
- 执行装饰过程。调用
outer_wrapper并传入show_message,将返回结果(即inner_wrapper)重新赋值给show_message。
show_message = outer_wrapper(show_message)
- 调用
show_message。
show_message()
此时,show_message 实际上指向的是 inner_wrapper 函数,因此输出会包含前后两条日志线,中间是核心业务逻辑。
第四阶段:使用 @ 语法糖
Python 提供了 @ 符号来简化“将函数作为参数传递并重新赋值”的过程。
- 保留之前定义的
outer_wrapper函数。 - 定义一个新的函数
send_notification,并在其定义行上方 添加@outer_wrapper。
@outer_wrapper
def send_notification():
print("发送通知邮件。")
- 直接调用
send_notification。你会发现它自动具备了“开始执行”和“执行结束”的日志功能,而无需手动执行send_notification = outer_wrapper(send_notification)。
第五阶段:处理参数与返回值
目前的装饰器有一个致命缺陷:它只能装饰没有任何参数的函数,并且无论原始函数返回什么,它都会返回 None(因为 inner_wrapper 没有显式返回值)。我们需要完善它。
- 修改
inner_wrapper的定义,使其接收任意数量的位置参数*args和关键字参数**kwargs。 - 在调用
original_func时,将这些参数传递进去。 - 捕获
original_func的返回值,并 在inner_wrapper末尾返回该值。
def smart_wrapper(original_func):
def inner_wrapper(*args, **kwargs):
print(f">> 日志: 函数 {original_func.__name__} 即将执行,参数: {args}, {kwargs}")
# 接收原始函数的返回值
result = original_func(*args, **kwargs)
print(f">> 日志: 函数 {original_func.__name__} 执行完毕,返回值: {result}")
return result
return inner_wrapper
- 定义一个带参数和返回值的函数进行测试。
@smart_wrapper
def add(a, b):
return a + b
- 调用
add(3, 5)。装饰器将正确打印日志并返回计算结果8。
第六阶段:保留原函数的元数据
当我们使用装饰器后,被装饰函数的元数据(如函数名 __name__、文档字符串 __doc__)会被替换为包装器的元数据。这可能会导致一些依赖反射的代码出错。
- 查看被装饰函数的名字。
print(add.__name__)
你会发现输出是 inner_wrapper,而不是 add。这显然不是我们想要的。
- 引入 Python 标准库
functools中的wraps装饰器。 - 在
inner_wrapper定义上方 添加@functools.wraps(original_func)。
import functools
def smart_wrapper(original_func):
@functools.wraps(original_func)
def inner_wrapper(*args, **kwargs):
print(f">> 日志: {original_func.__name__} 执行中...")
return original_func(*args, **kwargs)
return inner_wrapper
- 重新定义
add函数(使用新的smart_wrapper)。 - 再次打印
add.__name__。现在它会正确显示add。
第七阶段:实现带参数的装饰器
有时候我们需要装饰器本身接收参数,例如 @repeat(times=3),让函数重复执行 3 次。这需要三层嵌套结构。
- 定义最外层函数
repeat,它接收装饰器的参数(如times)。 - 在
repeat内部 定义中间层函数decorator,它接收真正的函数func。 - 在
decorator内部 定义最内层函数wrapper,它负责实际的业务逻辑和函数调用。
import functools
def repeat(times):
# 第一层:接收装饰器参数
def decorator(func):
# 第二层:接收被装饰的函数
@functools.wraps(func)
def wrapper(*args, **kwargs):
# 第三层:执行逻辑
results = []
for _ in range(times):
result = func(*args, **kwargs)
results.append(result)
return results
return wrapper
return decorator
- 使用带参数的装饰器装饰一个函数。
@repeat(times=3)
def greet(name):
print(f"Hello, {name}!")
- 调用
greet("Alice")。该函数将被执行 3 次。
装饰器执行流程图解
为了更直观地理解带参数装饰器的执行顺序,参考下方的逻辑流向。
装饰器的核心在于“闭包”和“函数作为参数传递”。通过层层包裹,我们在不修改原有业务逻辑代码的前提下,实现了日志记录、性能测试、权限校验、重复执行等横切关注点的功能。

暂无评论,快来抢沙发吧!