一、简介
在 Python 编程中,装饰器(Decorator)是一种强大的语法糖,它允许开发者在不修改原函数代码的情况下,动态增强函数功能。这种设计模式广泛应用于日志记录、权限验证、性能测试和缓存等场景,是 Python 元编程的重要组成部分。本文将从基础概念出发,逐步深入探讨装饰器的各种应用技巧。
二、装饰器基础原理
2.1 函数作为一等公民
在 Python 中,函数是一等公民(First-Class Citizen),这意味着它们可以:
作为变量赋值
作为参数传递
作为返回值返回
存储在数据结构中
以下示例展示了函数作为变量和参数的用法:
def greet(name):
return f"Hello, {name}!"
# 将函数赋值给变量
hello = greet
# 函数作为参数传递
def call_function(func, arg):
return func(arg)
print(call_function(hello, "Alice")) # 输出: Hello, Alice!
2.2 闭包(Closure)
闭包是装饰器的核心机制,它指的是一个函数可以访问并记住其外部作用域中的变量,即使外部函数已经执行完毕。
def outer(x):
def inner(y):
return x + y # 闭包捕获了外部变量x
return inner
add_five = outer(5)
print(add_five(3)) # 输出: 8
2.3 简单装饰器实现
装饰器本质上是一个高阶函数,它接收一个函数作为输入,并返回一个新的函数。以下是一个计算函数执行时间的装饰器示例:
import time
def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs) # 执行原函数
end_time = time.time()
print(f"函数 {func.__name__} 执行耗时: {end_time - start_time:.4f} 秒")
return result
return wrapper
@timer_decorator
def calculate_sum(n):
return sum(range(n+1))
print(calculate_sum(1000000))
# 输出:
# 函数 calculate_sum 执行耗时: 0.0520 秒
# 500000500000
这个装饰器的工作原理如下:
timer_decorator 接收 calculate_sum 函数作为参数
内部定义了 wrapper 函数来包裹原函数
返回 wrapper 函数替代原函数
@timer_decorator 语法糖等价于 calculate_sum = timer_decorator(calculate_sum)
三、装饰器进阶用法
3.1 带参数的装饰器
有时我们需要为装饰器传递额外参数,这可以通过创建一个三层嵌套函数实现:
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
result = None
for _ in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def say_hi():
print("Hi!")
say_hi() # 输出三次 Hi!
3.2 类装饰器
除了函数装饰器,Python 还支持使用类作为装饰器。类装饰器通过实现 __call__ 方法来实现:
class CountCalls:
def __init__(self, func):
self.func = func
self.num_calls = 0
def __call__(self, *args, **kwargs):
self.num_calls += 1
print(f"第 {self.num_calls} 次调用 {self.func.__name__}")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
say_hello() # 输出: 第 1 次调用 say_hello \n Hello!
say_hello() # 输出: 第 2 次调用 say_hello \n Hello!
3.3 保留函数元信息
当使用装饰器时,原函数的元信息(如 __name__、__doc__)会被覆盖。可以使用 functools.wraps 来保留这些信息:
import functools
def debug(func):
@functools.wraps(func) # 保留原函数元信息
def wrapper(*args, **kwargs):
print(f"调用 {func.__name__}({args}, {kwargs})")
result = func(*args, **kwargs)
print(f"{func.__name__} 返回 {result}")
return result
return wrapper
@debug
def add(a, b):
"""返回两数之和"""
return a + b
print(add(3, 5))
print(add.__name__) # 输出: add (而不是 wrapper)
print(add.__doc__) # 输出: 返回两数之和
3.4 多个装饰器叠加
多个装饰器可以叠加使用,它们会按照从下到上的顺序依次应用:
def make_bold(func):
def wrapper(*args, **kwargs):
return f"<b>{func(*args, **kwargs)}</b>"
return wrapper
def make_italic(func):
def wrapper(*args, **kwargs):
return f"<i>{func(*args, **kwargs)}</i>"
return wrapper
@make_bold
@make_italic
def greet(name):
return f"Hello, {name}!"
print(greet("Alice")) # 输出: <b><i>Hello, Alice!</i></b>
四、实战应用案例
4.1 日志记录
自动记录函数调用和返回值:
def log_decorator(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
with open('app.log', 'a') as f:
f.write(f"{func.__name__}({args}, {kwargs}) -> {result}\n")
return result
return wrapper
@log_decorator
def calculate(a, b):
return a * b
4.2 权限验证
在执行敏感操作前验证用户权限:
def requires_admin(func):
def wrapper(user, *args, **kwargs):
if user.get('role') != 'admin':
raise PermissionError("需要管理员权限")
return func(user, *args, **kwargs)
return wrapper
@requires_admin
def delete_user(user, username):
print(f"删除用户 {username}")
4.3 缓存机制
使用 functools.lru_cache 缓存函数结果:
import functools
@functools.lru_cache(maxsize=128)
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(50)) # 第一次计算较慢,后续调用将直接使用缓存结果
4.4 性能测试
统计函数执行时间并生成报告:
import time
import functools
def performance_report(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = time.perf_counter()
result = func(*args, **kwargs)
end_time = time.perf_counter()
print(f"{func.__name__} 执行时间: {end_time - start_time:.6f} 秒")
return result
return wrapper
@performance_report
def process_data(data):
# 模拟数据处理
time.sleep(1)
return len(data)
五、装饰器高级话题
5.1 装饰器类的参数化
结合类装饰器和参数化装饰器:
class Retry:
def __init__(self, max_attempts=3):
self.max_attempts = max_attempts
def __call__(self, func):
def wrapper(*args, **kwargs):
attempts = 0
while attempts < self.max_attempts:
try:
return func(*args, **kwargs)
except Exception as e:
attempts += 1
print(f"尝试 {attempts}/{self.max_attempts} 失败: {e}")
raise Exception("达到最大重试次数")
return wrapper
@Retry(max_attempts=5)
def connect_to_database():
# 模拟数据库连接,可能会失败
import random
if random.random() < 0.8:
raise ConnectionError("连接失败")
return "连接成功"
5.2 装饰器与异步函数
异步函数的装饰器需要使用 async def 和 await:
import asyncio
import time
def async_timer(func):
async def wrapper(*args, **kwargs):
start_time = time.time()
result = await func(*args, **kwargs)
end_time = time.time()
print(f"异步函数 {func.__name__} 执行耗时: {end_time - start_time:.4f} 秒")
return result
return wrapper
@async_timer
async def fetch_data(url):
await asyncio.sleep(1) # 模拟网络请求
return {"data": "example"}
5.3 装饰器与类方法
装饰器也可以应用于类中的方法:
def debug_method(func):
def wrapper(self, *args, **kwargs):
print(f"调用方法 {func.__name__} 来自实例 {self}")
return func(self, *args, **kwargs)
return wrapper
class MyClass:
@debug_method
def method(self, x):
return x * 2
六、总结
装饰器是 Python 中一种非常灵活且强大的工具,它允许我们在不修改原有代码的情况下增强函数或类的功能。通过理解装饰器的基本原理(函数作为一等公民和闭包),我们可以创建出各种实用的装饰器,用于日志记录、权限验证、缓存、性能测试等场景。
在实际应用中,我们需要注意以下几点:
使用 functools.wraps 保留原函数元信息
理解装饰器的执行顺序(从下到上)
合理设计带参数的装饰器
注意异步函数装饰器的特殊性
掌握装饰器是 Python 程序员进阶的关键一步,它不仅能让你的代码更加简洁优雅,还能提高代码的复用性和可维护性。希望通过本文的介绍,你对 Python 装饰器有了更深入的理解和掌握。