什么是装饰器?

装饰器:在不修改原函数代码的情况下,动态地扩展函数的功能。 装饰器本质上是一个接收函数作为参数并返回新函数的高阶函数通过@符号简洁地应用到目标函数上,常用于添加日志、权限验证、性能测试等横切关注点功能。

一、装饰器的分类

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] 函数执行完成

总结图示

复制代码
装饰器分类
├─ 按应用对象
│  ├─ 函数装饰器
│  └─ 类装饰器
├─ 按参数形式
│  ├─ 无参装饰器
│  └─ 带参装饰器
├─ 按实现方式
│  ├─ 函数实现
│  └─ 类实现
└─ 按功能场景
   ├─ 属性管理
   ├─ 缓存
   └─ 异步等
相关推荐
Johny_Zhao3 小时前
CentOS Stream 8 高可用 Kuboard 部署方案
linux·网络·python·网络安全·docker·信息安全·kubernetes·云计算·shell·yum源·系统运维·kuboard
站大爷IP3 小时前
精通einsum():多维数组操作的瑞士军刀
python
站大爷IP4 小时前
Python与MongoDB的亲密接触:从入门到实战的代码指南
python
Roc-xb5 小时前
/etc/profile.d/conda.sh: No such file or directory : numeric argument required
python·ubuntu·conda
粟悟饭&龟波功5 小时前
Java—— ArrayList 和 LinkedList 详解
java·开发语言
冷雨夜中漫步5 小时前
Java中如何使用lambda表达式分类groupby
java·开发语言·windows·llama
a4576368765 小时前
Objective-c Block 面试题
开发语言·macos·objective-c
Cai junhao5 小时前
【Qt】Qt控件
开发语言·c++·笔记·qt
uyeonashi6 小时前
【QT系统相关】QT网络
开发语言·网络·c++·qt
世由心生6 小时前
[从0到1]环境准备--anaconda与pycharm的安装
ide·python·pycharm