Python装饰器是接收函数并返回增强函数的特殊函数,用于添加日志、权限检查等功能而不修改原函数代码。通过@语法糖应用,结合functools.wraps保留元数据,利用闭包和函数一等公民特性实现功能增强,支持带参装饰和类装饰器,适用于横切关注点,提升代码复用性与可维护性。

Python装饰器,说白了,就是一种特殊类型的函数,它接收另一个函数作为输入,然后返回一个经过“增强”或“修改”的新函数。它的核心目的是在不改变原函数代码的情况下,为其添加额外的功能,比如日志记录、权限检查、性能监控等等。通过
@
要使用Python装饰器,最直接的方式就是定义一个装饰器函数,然后用
@
import functools
def my_simple_decorator(func):
"""
一个简单的装饰器,用于在函数执行前后打印信息。
"""
@functools.wraps(func) # 这一行非常重要,它能保留原函数的元数据
def wrapper(*args, **kwargs):
print(f"--- 准备执行函数: {func.__name__} ---")
result = func(*args, **kwargs) # 调用原始函数
print(f"--- 函数执行完毕: {func.__name__} ---")
return result
return wrapper
@my_simple_decorator
def say_hello(name):
"""一个简单的打招呼函数"""
print(f"Hello, {name}!")
return f"Greeting to {name}"
@my_simple_decorator
def add(a, b):
"""一个简单的加法函数"""
print(f"计算 {a} + {b}")
return a + b
# 调用被装饰的函数
say_hello("Alice")
print("-" * 20)
sum_result = add(10, 20)
print(f"加法结果: {sum_result}")
print("-" * 20)
# 看看不使用 functools.wraps 会有什么问题
def another_decorator(func):
def wrapper(*args, **kwargs):
print(f"Wrapper for {func.__name__}")
return func(*args, **kwargs)
return wrapper
@another_decorator
def my_func_without_wraps():
"""这个函数的文档字符串会丢失"""
pass
print(f"原始函数名: {say_hello.__name__}") # 输出 'say_hello'
print(f"包装函数名 (无wraps): {my_func_without_wraps.__name__}") # 输出 'wrapper'
print(f"原始函数文档: {say_hello.__doc__}") # 输出 '一个简单的打招呼函数'
print(f"包装函数文档 (无wraps): {my_func_without_wraps.__doc__}") # 输出 None在这个例子里,
my_simple_decorator
say_hello
add
wrapper
say_hello("Alice")wrapper
say_hello
@functools.wraps(func)
__name__
__doc__
我个人觉得,装饰器最核心的价值在于它提供了一种非常优雅的方式来处理所谓的“横切关注点”(cross-cutting concerns)。想象一下,你正在构建一个大型系统,里面有几十甚至上百个函数,而这些函数都需要:
立即学习“Python免费学习笔记(深入)”;
如果每次都把这些逻辑写在函数内部,代码会变得非常冗余,而且难以维护。一旦需求变动,比如日志格式要改,你得改动所有相关的函数。这简直就是一场灾难。
装饰器就像一个“功能增强器”,它把这些通用的、与核心业务逻辑无关但又必不可少的功能,从函数内部剥离出来,独立成一个模块。这样一来:
@[decorator_name]
说实话,刚开始接触装饰器的时候,可能会觉得有点绕,但一旦你理解了它背后的设计哲学,就会发现它在组织和管理复杂功能方面,简直是Python提供的一把利器。它让我们的代码更“干练”,也更“智能”。
要真正掌握装饰器,就得稍微深入一点,看看它背后的两个核心概念:函数作为一等公民和闭包。这块儿确实有点烧脑,但一旦你理解了,Python的很多“魔术”你就能看透了。
函数作为一等公民(First-Class Functions)
在Python中,函数不是简单的代码块,它们是“一等公民”。这意味着函数可以:
my_var = my_function
map(my_function, my_list)
[my_function1, my_function2]
正是因为函数可以像普通数据一样被操作,我们才能把一个函数(被装饰的函数)传给另一个函数(装饰器),并让装饰器返回一个新的函数。
闭包(Closures)
闭包,简单来说,就是一个函数和它被创建时所处的“环境”(即它能够访问的非全局变量)的组合。当一个内部函数引用了其外部函数作用域中的变量,并且外部函数执行完毕后,内部函数仍然能够访问这些变量时,我们就说形成了一个闭包。
在装饰器中,
wrapper
wrapper
my_simple_decorator
my_simple_decorator
func
my_simple_decorator
func
wrapper
我们再来看一下装饰器的执行流程:
定义阶段: 当你写下
@my_simple_decorator
def say_hello(name): ...
say_hello = my_simple_decorator(say_hello)
my_simple_decorator
say_hello
装饰器函数执行:
my_simple_decorator
say_hello
wrapper
wrapper
say_hello
func
my_simple_decorator
wrapper
赋值: 现在,原本的
say_hello
my_simple_decorator
wrapper
调用阶段: 当你调用
say_hello("Alice")wrapper
wrapper
say_hello
# 一个更纯粹的闭包例子,帮助理解
def outer_function(msg):
# msg 是外部函数的局部变量
def inner_function():
# inner_function 引用了外部函数的 msg 变量
print(msg)
return inner_function
# 创建闭包实例
hello_func = outer_function("Hello from closure!")
goodbye_func = outer_function("Goodbye from closure!")
# 调用闭包,即使 outer_function 已经执行完毕
hello_func() # 输出 "Hello from closure!"
goodbye_func() # 输出 "Goodbye from closure!"
# 装饰器中的 wrapper 函数就是这样的一个 inner_function,
# 它通过闭包机制“记住”了被装饰的原始函数。理解了闭包,你就能明白为什么
wrapper
func
my_simple_decorator
装饰器不仅仅能做简单的函数增强,它还能接受参数,甚至可以用类来实现。这为我们提供了更灵活、更强大的功能定制能力。
带参数的装饰器
有时候,你希望装饰器在应用时能接收一些配置参数。比如,一个日志装饰器可能需要你指定日志级别(INFO, DEBUG)或者日志文件名。这就需要一个三层嵌套的结构:
import functools
def log_decorator(level):
"""
一个带参数的日志装饰器。
level: 日志级别,如 'INFO', 'DEBUG'
"""
def decorator(func): # 这是第二层,真正的装饰器,接收被装饰的函数
@functools.wraps(func)
def wrapper(*args, **kwargs): # 这是第三层,包装函数
print(f"[{level}] 调用函数: {func.__name__} with args: {args}, kwargs: {kwargs}")
result = func(*args, **kwargs)
print(f"[{level}] 函数 {func.__name__} 返回: {result}")
return result
return wrapper
return decorator # 第一层返回第二层装饰器
@log_decorator("INFO") # 传入参数
def calculate_power(base, exp):
"""计算幂次"""
return base ** exp
@log_decorator("DEBUG") # 传入不同的参数
def debug_message(msg):
"""打印调试信息"""
print(f"DEBUG: {msg}")
print(calculate_power(2, 3))
print("-" * 20)
debug_message("This is a debug log.")这里的关键在于
log_decorator(level)
level
decorator
@log_decorator("INFO")log_decorator("INFO")decorator
calculate_power
decorator
wrapper
类装饰器
除了函数,我们也可以用类来作为装饰器。类装饰器通常在你需要装饰器拥有状态(state)时非常有用,比如统计函数被调用的次数。一个类要作为装饰器,它需要实现
__call__
import functools
class CallCounter:
"""
一个类装饰器,用于统计函数被调用的次数。
"""
def __init__(self, func):
# 构造函数接收被装饰的函数
functools.wraps(func)(self) # 同样用 wraps 复制元数据
self.func = func
self.calls = 0 # 初始化调用计数
def __call__(self, *args, **kwargs):
# 当被装饰的函数被调用时,实际上是这个 __call__ 方法被执行
self.calls += 1
print(f"函数 {self.func.__name__} 被调用了 {self.calls} 次。")
return self.func(*args, **kwargs)
@CallCounter
def perform_task(task_name):
"""执行一个任务"""
print(f"正在执行任务: {task_name}")
@CallCounter
def another_task():
"""另一个任务"""
print("执行另一个任务")
perform_task("清理数据")
perform_task("生成报告")
another_task()
another_task()
another_task()
print(f"perform_task 实际调用次数: {perform_task.calls}")
print(f"another_task 实际调用次数: {another_task.calls}")当
perform_task
@CallCounter
@CallCounter
CallCounter
perform_task
__init__
perform_task
perform_task("清理数据")CallCounter
__call__
self.calls
装饰器虽然强大,但在实际使用中也有些小坑和需要注意的地方。避免这些,能让你的代码更健壮,也更易于调试。
functools.wraps
这绝对是新手最容易忽略,也是最容易踩的坑。前面已经提到过,如果你不使用
functools.wraps
# 再次强调 functools.wraps 的作用
import functools
def no_wraps_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
def with_wraps_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@no_wraps_decorator
def my_func_a():
"""这是函数A的文档"""
pass
@with_wraps_decorator
def my_func_b():
"""这是函数B的文档"""
pass
print(f"my_func_a.__name__: {my_func_a.__name__}") # 输出 'wrapper'
print(f"my_func_a.__doc__: {my_func_a.__doc__}") # 输出 None
print(f"my_func_b.__name__: {my_func_b.__name__}") # 输出 'my_func_b'
print(f"my_func_b.__doc__: {my_func_b.__doc__}") # 输出 '这是函数B的文档'看到区别了吗?
functools.wraps
装饰器链的执行顺序
当一个函数被多个装饰器装饰时,它们的执行顺序是从下往上(或从内到外)。
def deco_a(func):
@functools.wraps(func)
def wrapper_a(*args, **kwargs):
print("--- 进入 deco_a ---")
result = func(*args, **kwargs)
print("--- 退出 deco_a ---")
return result
return wrapper_a
def deco_b(func):
@functools.wraps(func)
def wrapper_b(*args, **kwargs):
print("--- 进入 deco_b ---")
result = func(*args, **kwargs)
print("--- 退出 deco_b ---")
return result
return wrapper_b
@deco_a
@deco_b
def target_function():
print("--- 目标函数执行 ---")
target_function()输出会是:
--- 进入 deco_a --- --- 进入 deco_b --- --- 目标函数执行 --- --- 退出 deco_b --- --- 退出 deco_a ---
这表明,
@deco_b
target_function
deco_a
deco_b
deco_a
wrapper_a
deco_b
避免过度使用装饰器
装饰器固然好用,但并非银弹。过度使用或滥用装饰器,可能会让代码变得难以阅读和调试。当你看到一个函数上面挂了五六个装饰器时,你可能需要停下来思考一下,这些功能是否真的都适合用装饰器来实现,或者有没有更简洁的组织方式。有时候,简单的函数组合或者继承可能会是更好的选择。装饰器最适合处理那些与核心业务逻辑正交的、可复用的横切关注点。
错误处理与异常传播
装饰器内部在调用被装饰函数时,应该妥善处理可能抛出的异常。通常,我们会让异常自然地从被装饰函数传播出来,除非装饰器本身需要捕获并处理这些异常(比如记录错误日志,然后重新抛出)。
def error_logging_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
print(f"ERROR: 函数 {func.__name__} 发生异常: {e}")
raise # 重新抛出异常,让调用者也能感知以上就是Python如何使用装饰器_Python装饰器原理与实践指南的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号