在 Python 开发中,常需在函数执行前后附加额外功能,如日志记录、性能监控等。若无装饰器,需在每个函数中重复编写相关代码,既冗余又破坏函数单一职责原则。Python 的装饰器提供了一种优雅的解决方案,它能在不修改原函数逻辑的前提下,为其增加新功能。装饰器的本质是“包装”函数或类,其常见形式是通过一个函数接收另一个函数,并返回一个新的函数。
装饰器基础
理解装饰器需先掌握两个关键概念:函数是一等公民和闭包。在 Python 中,函数可被赋值给变量、作为参数传递,也可作为返回值返回,这为装饰器的实现提供了基础。闭包则是内部函数引用外部函数变量的现象,即使外部函数已结束运行,内部函数仍可使用这些变量。
基于此,装饰器的核心是定义一个函数,接收另一个函数作为参数并返回新函数,从而在不修改原函数代码的情况下,为其添加额外功能。例如,以下是一个简单的装饰器示例:
def simple_decorator(func):
def wrapper():
print("Before the function runs")
func()
print("After the function runs")
return wrapper
@simple_decorator
def say_hello():
print("Hello!")
say_hello()
输出结果为:
Before the function runs
Hello!
After the function runs
装饰器的进阶用法
保留函数元信息:functools.wraps
使用装饰器时,原函数的元信息(如 __name__、__doc__ 等)会被替换为包装函数的信息。为保留原函数的元信息,需使用 functools.wraps。例如:
from functools import wraps
def simple_decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@simple_decorator
def greet(name):
"""Say hello to someone"""
return f"Hello, {name}!"
print(greet.__name__) # greet
print(greet.__doc__) # Say hello to someone
类装饰器
除了函数,也可用类实现装饰器。关键在于定义 __call__ 方法,使类实例能像函数一样被调用。例如:
class Repeat:
def __init__(self, times):
self.times = times
def __call__(self, func):
def wrapper(*args, **kwargs):
result = None
for _ in range(self.times):
result = func(*args, **kwargs)
return result
return wrapper
@Repeat(3)
def say_hi(name):
print(f"Hi, {name}!")
say_hi("Alice")
装饰器叠加
Python 允许在一个函数上叠加多个装饰器,执行顺序是自下而上。例如:
def decorator_a(func):
def wrapper(*args, **kwargs):
print("Decorator A before")
result = func(*args, **kwargs)
print("Decorator A after")
return result
return wrapper
def decorator_b(func):
def wrapper(*args, **kwargs):
print("Decorator B before")
result = func(*args, **kwargs)
print("Decorator B after")
return result
return wrapper
@decorator_a
@decorator_b
def say_hello():
print("Hello!")
say_hello()
输出结果为:
Decorator A before
Decorator B before
Hello!
Decorator B after
Decorator A after
带参数的装饰器工厂
若希望装饰器本身也能接收参数,需再多写一层函数,即装饰器工厂。例如:
from functools import wraps
def repeat(times):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
实用库中的装饰器
装饰器在 Python 标准库和第三方框架中应用广泛。例如,标准库中的 @staticmethod、@classmethod、@property 和 @functools.lru_cache 等装饰器,以及第三方框架 Flask 的路由装饰器、Django 的权限装饰器和 Click 的命令行装饰器等,都极大地简化了代码,提高了开发效率。
最佳实践与注意事项
避免滥用:装饰器适用于处理横切逻辑,如日志、缓存、权限验证等,但不宜过度使用,以免影响代码可读性。
始终使用 functools.wraps:为保留原函数的元信息,自定义装饰器应加上 @wraps。
注意副作用:装饰器可能改变函数的调用方式或引入缓存、权限等逻辑,需清楚其可能带来的副作用,并在必要时提供关闭或绕过的机制。
保持可读性:复杂的装饰器逻辑应写清楚,或在文档和注释中解释清楚其作用和顺序,必要时可用类装饰器或明确的函数调用替代。
关注性能:装饰器会增加函数调用层级,复杂装饰器或过多装饰器可能影响性能,需在必要时通过性能分析工具确认是否存在瓶颈。
总结
本文介绍了 Python 装饰器的基础语法、进阶用法及在标准库和第三方框架中的应用。装饰器能有效剥离横切逻辑,减少重复代码,提高代码可读性和扩展性。使用时需遵循最佳实践,避免滥用,关注可读性、性能和副作用,从而在合适的场景中自然地使用装饰器,提升代码质量。