装饰器
装饰器是 Python 中用于在不修改原函数定义的前提下扩展功能的高阶函数。@ 符号只是语法糖,它把 func = decorator(func) 的赋值操作提前到函数定义阶段,让代码更整洁。
装饰器的基本原理
装饰器本质上是一个接收函数作为参数并返回函数的 callable。最朴素的实现不用 @ 符号,而是手动包装:
def add_log(func):
def wrapper(*args, **kwargs):
print(f"[LOG] 调用 {func.__name__}")
return func(*args, **kwargs)
return wrapper
def greet(name):
"""向用户问好"""
return f"你好, {name}"
# 手动包装
greet = add_log(greet)
print(greet("航仔")) # [LOG] 调用 greet → 你好, 航仔
@ 语法糖把上述赋值自动化。下面的写法与上面完全等价:
@add_log
def greet(name):
return f"你好, {name}"
functools.wraps 与元信息保留
如果不做处理,装饰器返回的 wrapper 函数会覆盖原函数的元信息(__name__、__doc__、__annotations__ 等),导致调试和文档生成时看到的信息都是 wrapper 的:
def bad_decorator(func):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@bad_decorator
def target():
"""目标函数"""
pass
print(target.__name__) # wrapper —— 丢失了原函数名
print(target.__doc__) # None —— 丢失了文档字符串
functools.wraps 使用 functools.update_wrapper 在内部把原函数的 __module__、__name__、__qualname__、__annotations__、__doc__ 以及 __wrapped__ 等属性复制到 wrapper 上,是编写自定义装饰器的必加项:
import functools
def good_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
@good_decorator
def target():
"""目标函数"""
pass
print(target.__name__) # target
print(target.__doc__) # 目标函数
print(target.__wrapped__) # <function target ...> 可通过它访问原函数
带参数的装饰器
当装饰器本身需要配置(如重试次数、缓存大小),需要三层嵌套:外层接收配置参数并返回真正的装饰器,中层接收被装饰函数,内层是实际 wrapper:
import time
import functools
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:
print(f"第 {attempt} 次失败: {e}")
if attempt == max_attempts:
raise
time.sleep(delay)
return wrapper
return decorator
@retry(max_attempts=2, delay=0.1)
def fetch_data(url):
if "bad" in url:
raise ConnectionError("连接超时")
return f"数据: {url}"
print(fetch_data("good_url")) # 正常返回
# print(fetch_data("bad_url")) # 重试2次后抛出 ConnectionError
注意 @retry() 必须带括号,即使不传参数也要写 @retry(),否则 decorator 会被当成直接装饰器使用,导致参数传递层级错乱。
类装饰器
装饰器不仅可以是函数,也可以是实现了 __call__ 的类。类装饰器的优势在于可以保存状态、支持继承和更复杂的配置:
import functools
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.func.__name__} 被调用了 {self.count} 次")
return self.func(*args, **kwargs)
@CountCalls
def say_hello():
return "hello"
say_hello() # say_hello 被调用了 1 次
say_hello() # say_hello 被调用了 2 次
类装饰器也可以用来装饰类本身,例如自动注册子类、添加类属性或验证接口:
def singleton(cls):
"""单例模式装饰器:确保类只有一个实例"""
instances = {}
def wrapper(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return wrapper
@singleton
class Database:
def __init__(self):
print("初始化数据库连接")
db1 = Database()
db2 = Database()
print(db1 is db2) # True —— 两次获取的是同一实例
多个装饰器叠加
多个装饰器从上到下书写,执行顺序是从下往上(靠近函数定义的装饰器先执行):
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 demo():
print("原函数")
demo()
# 输出顺序:A 之前 → B 之前 → 原函数 → B 之后 → A 之后
这等价于 demo = decorator_a(decorator_b(demo))。理解这个顺序对调试多层装饰器(如权限检查在最外层、日志在中间、性能计时在最内层)至关重要。
内置装饰器深入
@staticmethod 与 @classmethod
@staticmethod 把方法变成不依赖实例和类的普通函数,不会自动接收 self 或 cls,常用于与类概念相关但不需要访问类状态的辅助函数。@classmethod 的第一个参数是类本身 cls,常用于工厂方法或需要操作类状态的场景:
class Employee:
_company = "飞翔科技"
def __init__(self, name, salary):
self.name = name
self.salary = salary
@staticmethod
def validate_id(emp_id: str) -> bool:
"""校验工号格式,不依赖任何实例/类数据"""
return isinstance(emp_id, str) and len(emp_id) == 6
@classmethod
def from_dict(cls, data: dict):
"""从字典创建实例,可被子类复用"""
return cls(data["name"], data["salary"])
@classmethod
def set_company(cls, name: str):
"""修改类级别的公司名"""
cls._company = name
print(Employee.validate_id("FX0001")) # False —— 长度不足
emp = Employee.from_dict({"name": "航仔", "salary": 18888})
@property
@property 把方法变成只读属性访问,调用时不用加括号。配合 setter 和 deleter 可以实现受控的属性修改和删除:
class Employee:
def __init__(self, name, base_salary):
self._name = name
self._base_salary = base_salary
@property
def name(self):
"""只读属性:员工姓名不允许外部修改"""
return self._name
@property
def annual_salary(self):
"""年薪自动计算"""
return self._base_salary * 12
@property
def base_salary(self):
return self._base_salary
@base_salary.setter
def base_salary(self, value):
if value < 2300:
raise ValueError("工资不能低于最低工资标准")
self._base_salary = value
@base_salary.deleter
def base_salary(self):
raise AttributeError("禁止删除工资属性")
emp = Employee("航仔", 18888)
print(emp.annual_salary) # 226656 —— 像属性一样访问
emp.base_salary = 20000 # 通过 setter 修改
# emp.base_salary = 2000 # ValueError: 工资不能低于最低工资标准
# del emp.base_salary # AttributeError: 禁止删除工资属性
实际应用场景
权限检查装饰器:在 Web 框架或内部系统中,装饰器是权限控制的经典实现方式:
def require_role(role: str):
def decorator(func):
@functools.wraps(func)
def wrapper(user, *args, **kwargs):
if user.get("role") != role:
raise PermissionError(f"需要 {role} 权限")
return func(user, *args, **kwargs)
return wrapper
return decorator
@require_role("admin")
def delete_employee(user, emp_id: str):
return f"已删除员工 {emp_id}"
admin = {"name": "翼王", "role": "admin"}
guest = {"name": "访客", "role": "guest"}
print(delete_employee(admin, "FX001"))
# print(delete_employee(guest, "FX001")) # PermissionError
缓存装饰器:利用 functools.lru_cache 或自定义字典缓存耗时函数的返回值,避免重复计算:
import functools
@functools.lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
print(fibonacci(100)) # 瞬间完成,递归结果自动缓存
print(fibonacci.cache_info()) # CacheInfo(hits=98, misses=101, ...)
lru_cache 要求被装饰函数的参数必须是可哈希的(hashable),列表和字典不能直接作为参数传入。maxsize=None 表示无限制缓存,适合参数空间有限的场景。