Python 装饰器的深入理解与应用:从入门到实战
Python 装饰器的深入理解与应用:从入门到实战
前言
装饰器(Decorator)是 Python 中最优雅、最强大的特性之一。它允许我们在不修改原函数代码的前提下,动态地给函数添加功能。无论是 Web 开发中的权限验证、API 速率限制,还是日常开发中的日志记录、性能分析,装饰器都扮演着至关重要的角色。
本文将带你从装饰器的基础概念出发,逐步深入理解其工作原理,并通过大量实战案例掌握装饰器的高级用法。无论你是 Python 初学者还是有一定经验的开发者,相信都能从中获得新的启发。
一、什么是装饰器?
1.1 核心概念
装饰器本质上是一个高阶函数——它接收一个函数作为参数,并返回一个新的函数。用简单的代码表示就是:
def decorator(func):
def wrapper(*args, **kwargs):
# 在调用原函数之前执行
result = func(*args, **kwargs)
# 在调用原函数之后执行
return result
return wrapper
1.2 语法糖:@符号
Python 提供了 INLINE_CODE_0 语法糖来简化装饰器的使用:
@decorator
def my_function():
pass
# 等价于
my_function = decorator(my_function)
这种写法更加简洁直观,也是 Python 社区广泛采用的标准写法。
二、装饰器的工作原理
2.1 函数是第一等公民
理解装饰器的关键在于理解 Python 中函数是第一等公民(First-Class Citizen)的概念。这意味着:
- 函数可以作为参数传递给其他函数
- 函数可以作为另一个函数的返回值
- 函数可以赋值给变量
def greet(name):
return f"Hello, {name}!"
# 函数赋值给变量
say_hello = greet
print(say_hello("Alice")) # Hello, Alice!
# 函数作为参数
def call_func(func, arg):
return func(arg)
print(call_func(greet, "Bob")) # Hello, Bob!
# 函数作为返回值
def create_greeter(prefix):
def greeter(name):
return f"{prefix}, {name}!"
return greeter
formal_greet = create_greeter("Good morning")
print(formal_greet("Charlie")) # Good morning, Charlie!
2.2 闭包的作用
装饰器依赖闭包(Closure)来保存状态。闭包是指内层函数引用了外层函数的变量,即使外层函数已经执行完毕,这些变量仍然可以被内层函数访问。
def counter_decorator(func):
count = 0 # 这个变量会被闭包保存
def wrapper(*args, **kwargs):
nonlocal count
count += 1
print(f"函数 {func.__name__} 被调用了 {count} 次")
return func(*args, **kwargs)
return wrapper
@counter_decorator
def say_hello():
print("Hello!")
say_hello() # 函数 say_hello 被调用了 1 次
say_hello() # 函数 say_hello 被调用了 2 次
三、基础装饰器实战
3.1 日志记录装饰器
日志记录是装饰器最常见的应用场景之一:
import functools
from datetime import datetime
def log_calls(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"[{timestamp}] 调用函数:{func.__name__}")
print(f" 参数:args={args}, kwargs={kwargs}")
result = func(*args, **kwargs)
print(f" 返回值:{result}")
return result
return wrapper
@log_calls
def add(a, b):
return a + b
@log_calls
def multiply(a, b, c=1):
return a * b * c
add(3, 5)
multiply(2, 4, c=3)
3.2 性能分析装饰器
用于测量函数执行时间,帮助定位性能瓶颈:
import functools
import time
def timing(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
elapsed = end - start
print(f"函数 {func.__name__} 执行耗时:{elapsed:.6f} 秒")
return result
return wrapper
@timing
def slow_function():
time.sleep(1)
return "完成"
@timing
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
slow_function()
fibonacci(20)
3.3 重试机制装饰器
在网络请求等不稳定场景中非常实用:
import functools
import time
import random
def retry(max_attempts=3, delay=1, backoff=2, exceptions=(Exception,)):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
current_delay = delay
last_exception = None
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except exceptions as e:
last_exception = e
if attempt < max_attempts - 1:
print(f"尝试 {attempt + 1}/{max_attempts} 失败:{e}")
print(f"等待 {current_delay} 秒后重试...")
time.sleep(current_delay)
current_delay *= backoff
else:
print(f"达到最大重试次数 {max_attempts}")
raise last_exception
return wrapper
return decorator
@retry(max_attempts=3, delay=0.5, backoff=2)
def unstable_api():
if random.random() < 0.7:
raise ConnectionError("网络连接失败")
return "API 调用成功"
try:
result = unstable_api()
print(result)
except Exception as e:
print(f"最终失败:{e}")
四、带参数的装饰器
带参数的装饰器需要三层嵌套函数:
def repeat(times):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
results = []
for _ in range(times):
results.append(func(*args, **kwargs))
return results
return wrapper
return decorator
@repeat(times=3)
def roll_dice():
import random
return random.randint(1, 6)
print(roll_dice()) # [3, 5, 2]
五、类装饰器
装饰器不仅可以装饰函数,还可以装饰类:
def singleton(cls):
instances = {}
@functools.wraps(cls)
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Database:
def __init__(self):
self.connection = "已连接"
def query(self, sql):
return f"执行查询:{sql}"
db1 = Database()
db2 = Database()
print(db1 is db2) # True,是同一个实例
六、多个装饰器的叠加
Python 允许给同一个函数应用多个装饰器,执行顺序是从内到外:
def decorator_a(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("[A] 前置处理")
result = func(*args, **kwargs)
print("[A] 后置处理")
return result
return wrapper
def decorator_b(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print("[B] 前置处理")
result = func(*args, **kwargs)
print("[B] 后置处理")
return result
return wrapper
@decorator_a
@decorator_b
def test_func():
print("执行原函数")
test_func()
# 输出顺序:
# [A] 前置处理
# [B] 前置处理
# 执行原函数
# [B] 后置处理
# [A] 后置处理
七、实际应用场景
7.1 Web 框架中的权限验证
def require_auth(func):
@functools.wraps(func)
def wrapper(request, *args, **kwargs):
if not request.user.is_authenticated:
return {"error": "未授权", "status": 401}
return func(request, *args, **kwargs)
return wrapper
def require_admin(func):
@functools.wraps(func)
def wrapper(request, *args, **kwargs):
if not request.user.is_admin:
return {"error": "需要管理员权限", "status": 403}
return func(request, *args, **kwargs)
return wrapper
@require_auth
@require_admin
def delete_user(request, user_id):
return {"success": True}
7.2 API 速率限制
import time
from collections import defaultdict
def rate_limit(max_calls, period):
calls = defaultdict(list)
def decorator(func):
@functools.wraps(func)
def wrapper(client_id, *args, **kwargs):
now = time.time()
calls[client_id] = [t for t in calls[client_id] if now - t < period]
if len(calls[client_id]) >= max_calls:
return {"error": "请求过于频繁", "status": 429}
calls[client_id].append(now)
return func(client_id, *args, **kwargs)
return wrapper
return decorator
@rate_limit(max_calls=5, period=60)
def api_request(client_id, endpoint):
return {"data": f"来自 {endpoint} 的数据"}
7.3 缓存装饰器(记忆化)
def memoize(func):
cache = {}
@functools.wraps(func)
def wrapper(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return wrapper
@memoize
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(50)) # 瞬间完成
八、最佳实践与注意事项
8.1 使用 functools.wraps
始终使用 INLINE_CODE_1 来保留原函数的元数据。
8.2 保持装饰器的通用性
- 使用 INLINE_CODE_2 和 INLINE_CODE_3 接收任意参数
- 确保返回值的类型与原函数一致
- 处理好异常情况,避免吞掉错误
8.3 装饰器类
对于复杂的装饰器,可以使用类来实现:
class CountCalls:
def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func
self.count = 0
def __call__(self, *args, **kwargs):
self.count += 1
print(f"调用次数:{self.count}")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
print("Hello!")
九、总结
装饰器是 Python 中最优雅的特性之一,它体现了开放 - 封闭原则——对扩展开放,对修改封闭。通过本文的学习,你应该掌握了:
- 基础概念:装饰器的本质是高阶函数
- 核心原理:函数作为第一等公民和闭包机制
- 常见模式:日志、性能分析、重试、缓存等
- 高级用法:带参数装饰器、类装饰器、装饰器叠加
- 实战应用:权限验证、速率限制、API 设计
- 最佳实践:使用 wraps、保持通用性、异常处理
装饰器的强大之处在于它能让代码更加模块化、可复用和易维护。当你发现多个函数有相同的横切关注点时,装饰器往往是最佳解决方案。
记住:好的装饰器应该是透明的——它增强功能但不改变原函数的核心行为。掌握这个原则,你就能写出优雅的装饰器代码。
作者:折腾虾
技术栈:Python 3.8+
参考资料:Python 官方文档、Flask/Django 源码分析