Python装饰器是日常开发中频繁使用的特性,但大多数开发者停留在@staticmethod或@login_required的层面。本文将从闭包原理出发,逐步深入到参数化装饰器、类装饰器,最终触及自定义元类,帮助你建立完整的装饰器知识体系。
装饰器本质上是一个接收函数作为参数并返回函数的闭包。以下代码展示了最基础的装饰器实现:
def my_decorator(func):
def wrapper(*args, **kwargs):
print("调用前")
result = func(*args, **kwargs)
print("调用后")
return result
return wrapper
@my_decorator
def say_hello():
print("Hello")
say_hello()
输出结果为:
调用前
Hello
调用后
这里的关键在于wrapper函数形成了闭包,它记住了外部作用域中的func变量。理解这一点,才能明白为什么装饰器可以叠加使用:多个装饰器本质上是从内到外依次嵌套调用。
实际项目中,我们常常需要给装饰器传递参数。例如一个带超时设置的装饰器:
import functools
import time
def retry(max_attempts=3, delay=1):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts:
raise
time.sleep(delay)
print(f"第 attempt 次失败,正在重试...")
return wrapper
return decorator
@retry(max_attempts=5, delay=2)
def fetch_data():
import random
if random.random() < 0.7:
raise ConnectionError("网络波动")
return "数据获取成功"
参数化装饰器的核心技巧是增加一层嵌套:最外层接收参数,中间层是装饰器本身,最内层是包装函数。functools.wraps的使用至关重要,它能保留原函数的元数据,避免调试时函数名被替换为wrapper的困扰。
当装饰器需要维护状态时,类装饰器比函数装饰器更加清晰。以下实现了一个简单的速率限制装饰器:
import time
class RateLimiter:
def __init__(self, max_calls, period):
self.max_calls = max_calls
self.period = period
self.calls = []
def __call__(self, func):
def wrapper(*args, **kwargs):
now = time.time()
self.calls = [c for c in self.calls if now - c < self.period]
if len(self.calls) >= self.max_calls:
raise RuntimeError("请求过于频繁,请稍后再试")
self.calls.append(now)
return func(*args, **kwargs)
return wrapper
@RateLimiter(max_calls=5, period=60)
def api_endpoint():
return {"status": "ok"}
类装饰器利用__init__接收配置参数,__call__返回包装函数。这种方式天然支持状态管理,无需借助闭包中的可变对象(如列表或字典)。
在类中定义装饰器时,需要特别注意self的绑定问题。如果直接用函数装饰器装饰实例方法,self参数会丢失。正确的做法是实现描述符协议:
class CachedProperty:
def __init__(self, func):
self.func = func
self.name = func.__name__
def __get__(self, instance, owner):
if instance is None:
return self
value = self.func(instance)
setattr(instance, self.name, value)
return value
class DataProcessor:
def __init__(self, data):
self.data = data
@CachedProperty
def heavy_computation(self):
print("执行耗时计算...")
return sum(x ** 2 for x in self.data)
processor = DataProcessor(range(1000000))
print(processor.heavy_computation) # 首次计算
print(processor.heavy_computation) # 直接返回缓存值
CachedProperty通过__get__方法拦截属性访问,仅在首次访问时执行计算,后续直接从实例字典中读取。这是Django中cached_property的简化实现。
元类是类的类,它控制类的创建过程。通过自定义元类,我们可以在类定义阶段注入装饰器逻辑:
class AutoRegisterMeta(type):
registry = {}
def __new__(mcs, name, bases, namespace):
cls = super().__new__(mcs, name, bases, namespace)
if name != 'BasePlugin':
mcs.registry[name.lower()] = cls
return cls
class BasePlugin(metaclass=AutoRegisterMeta):
def execute(self):
raise NotImplementedError
class EmailPlugin(BasePlugin):
def execute(self):
return "发送邮件"
class SmsPlugin(BasePlugin):
def execute(self):
return "发送短信"
print(AutoRegisterMeta.registry)
# 输出: {'emailplugin': EmailPlugin, 'smsplugin': SmsPlugin}
在这个例子中,AutoRegisterMeta在类创建时自动将子类注册到字典中。这种模式在插件系统、ORM模型注册等场景中非常实用。
更进一步,我们可以用元类实现类似Django ORM的字段声明语法:
class Field:
def __init__(self, name, field_type):
self.name = name
self.field_type = field_type
class ModelMeta(type):
def __new__(mcs, name, bases, namespace):
fields = {}
for key, value in list(namespace.items()):
if isinstance(value, Field):
fields[key] = value
namespace.pop(key)
namespace['_fields'] = fields
return super().__new__(mcs, name, bases, namespace)
class Model(metaclass=ModelMeta):
pass
class User(Model):
id = Field('id', 'INTEGER')
name = Field('name', 'VARCHAR')
email = Field('email', 'VARCHAR')
print(User._fields)
# 输出: {'id': Field对象, 'name': Field对象, 'email': Field对象}
functools.wraps:保留原函数的__name__、__doc__等属性。@a @b def f()等价于f = a(b(f))。@classmethod时,装饰器要放在classmethod之上,即@decorator @classmethod。typing.ParamSpec保持装饰器对函数签名的支持:from typing import ParamSpec, TypeVar, Callable
P = ParamSpec('P')
T = TypeVar('T')
def transparent_decorator(func: Callable[P, T]) -> Callable[P, T]:
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
return func(*args, **kwargs)
return wrapper
从简单的函数装饰器到自定义元类,Python的装饰器机制提供了极大的灵活性。掌握这些技术后,你可以编写出更加简洁、可复用且易于维护的代码。建议从实际项目中的日志记录、权限校验等需求入手,逐步尝试更复杂的装饰器模式。