装饰器:在不修改原函数代码的情况下,动态地扩展函数的功能。 装饰器本质上是一个接收函数作为参数并返回新函数的高阶函数 ,通过@
符号简洁地应用到目标函数上,常用于添加日志、权限验证、性能测试等横切关注点功能。
一、装饰器的分类
1. 按应用对象分类
(1)函数装饰器(最常见)
修饰普通函数或方法:
python
def log(func): # ① 装饰器函数(接收被装饰函数)
def wrapper(*args, **kwargs): # ② 包装函数(实际替换原函数)
print(f"调用函数: {func.__name__}") # ③ 新增功能
return func(*args, **kwargs) # ④ 调用原函数
return wrapper # ⑤ 返回包装后的函数
@log # ⑥ 应用装饰器
def greet(name): # ⑦ 被装饰函数
print(f"Hello, {name}")
greet("Alice") # ⑧ 调用被装饰函数
调用函数: greet
"Hello, Alice"
(2)类装饰器
修饰整个类或通过类实现装饰逻辑:
python
def add_method(cls): # 类装饰器函数,接收被装饰的类
cls.new_method = lambda self: print("动态添加的方法") # 动态添加实例方法
return cls # 必须返回修改后的类(与函数装饰器返回函数不同)
@add_method # 等价于 MyClass = add_method(MyClass)
class MyClass:
pass
obj = MyClass()
obj.new_method() # 输出: 动态添加的方法
2. 按参数形式分类
(1)无参装饰器
直接接收被装饰函数:
python
def cache(func): # ① 接收被装饰函数(如fibonacci)
_cache = {} # ② 创建缓存字典(闭包特性保存状态)
def wrapper(*args): # ③ 包装函数
if args not in _cache: # ④ 检查是否已缓存
_cache[args] = func(*args) # ⑤ 未缓存则计算结果
return _cache[args] # ⑥ 返回缓存结果
return wrapper # ⑦ 返回包装后的函数
@cache
def fibonacci(n):
return n if n < 2 else fibonacci(n-1) + fibonacci(n-2)
(2)带参装饰器
外层接收额外参数,返回真正的装饰器:
python
import random
import time
# 定义重试装饰器
def retry(max_attempts):
def decorator(func):
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1: # 最后一次失败
print("❌ 达到最大重试次数,放弃")
raise
wait_time = (attempt + 1) * 2 # 重试等待时间(指数退避)
print(f"⚠️ 第 {attempt + 1} 次失败: {e}, {wait_time}秒后重试...")
time.sleep(wait_time)
return None
return wrapper
return decorator
# 模拟不稳定的API(50%失败率)
@retry(max_attempts=3)
def get_weather(city):
if random.random() < 0.5: # 50%概率失败
raise ConnectionError(f"无法获取{city}的天气数据")
return f"🌤 {city}: 晴天 25°C"
# 测试
print("=== 第一次调用 ===")
print(get_weather("北京")) # 可能成功或触发重试
print("\n=== 第二次调用 ===")
print(get_weather("上海")) # 另一次独立调用
# === 第一次调用 ===
# ⚠️ 第 1 次失败: 无法获取北京的天气数据, 2秒后重试...
# 🌤 北京: 晴天 25°C
#
# === 第二次调用 ===
# ⚠️ 第 1 次失败: 无法获取上海的天气数据, 2秒后重试...
# 🌤 上海: 晴天 25°C
3. 按实现方式分类
(1)函数实现装饰器
通过嵌套函数实现(前文示例均为函数实现)。
(2)类实现装饰器
通过类的 __call__
方法实现:
python
class CountCalls:
#双下划线方法在类实例化时自动调用,用于初始化对象。
def __init__(self, func): #构造方法
self.func = func
self.calls = 0
def __call__(self, *args, **kwargs): #让类的实例可以像函数一样被调用(obj())
self.calls += 1
print(f"第 {self.calls} 次调用")
return self.func(*args, **kwargs) # 调用原始函数
#当调用 say_hello() 时,实际上是调用了 CountCalls 实例的 __call__ 方法,从而实现了装饰器的功能(记录调用次数并执行原函数)。
@CountCalls
def say_hello():
print("Hello!")
#**CountCalls(say_hello)** 会调用 __init__,创建一个 CountCalls 实例,并把 say_hello 函数存储到 self.func,同时初始化 self.calls = 0。
#**say_hello 不再是原来的函数**,而是 CountCalls 的一个实例,但仍然可以像函数一样调用(因为实现了 __call__)。
say_hello() # 输出: 第 1 次调用 → Hello!
say_hello() # 输出: 第 2 次调用 → Hello!
4. 按功能场景分类
类型 | 内置/第三方示例 | 作用 |
---|---|---|
属性管理 | @property |
将方法转为属性访问 |
静态方法 | @staticmethod |
定义不依赖实例的方法 |
类方法 | @classmethod |
定义操作类而非实例的方法 |
缓存 | @functools.lru_cache |
自动缓存函数结果 |
异步 | @asyncio.coroutine |
标记协程函数 |
5. 特殊类型
(1)装饰器工厂
返回装饰器的函数(即带参装饰器的外层):
python
def decorator_factory(prefix): # (1) 装饰器工厂
def decorator(func): # (2) 真正的装饰器
def wrapper(*args, **kwargs): # (3) 包装函数
print(f"[{prefix}] 函数被调用")
return func(*args, **kwargs)
return wrapper
return decorator # 返回装饰器
@decorator_factory("DEBUG") # 调用工厂,先接受参数,再返回一个装饰器
def test():
pass
test() # 输出: [DEBUG] 函数被调用
@decorator_factory("INFO")
def hello():
print("Hello!")
@decorator_factory("ERROR")
def fail():
print("Failed!")
hello() # 输出: [INFO] 函数被调用 → Hello!
fail() # 输出: [ERROR] 函数被调用 → Failed!
(2)多层装饰器
多个装饰器叠加使用:
python
@log
@timer
@cache
def complex_operation():
print("执行原始函数")
return 42
complex_operation()
# 等价于: complex_operation = log(timer(cache(complex_operation)))
[cache] 检查缓存...
执行原始函数
[cache] 结果已缓存
[timer] 耗时: 0.001s
[log] 函数执行完成
总结图示
装饰器分类
├─ 按应用对象
│ ├─ 函数装饰器
│ └─ 类装饰器
├─ 按参数形式
│ ├─ 无参装饰器
│ └─ 带参装饰器
├─ 按实现方式
│ ├─ 函数实现
│ └─ 类实现
└─ 按功能场景
├─ 属性管理
├─ 缓存
└─ 异步等