目录

Python 装饰器

学习目标

  • 理解装饰器的概念和工作原理
  • 掌握函数装饰器和类装饰器的编写
  • 学会使用带参数的装饰器
  • 理解多个装饰器的叠加顺序

https://img.zhaojq.top/20260730221700903.jpg
Python装饰器原理

1. 装饰器基础

**装饰器(Decorator)**本质上是一个高阶函数,它接收一个函数作为参数,并返回一个新的函数。

1.1 最简单的装饰器

def my_decorator(func):
    """简单装饰器"""
    def wrapper():
        print("函数执行前")
        func()
        print("函数执行后")
    return wrapper

def say_hello():
    print("你好!")

# 手动装饰
say_hello = my_decorator(say_hello)
say_hello()

输出:

函数执行前
你好!
函数执行后

1.2 使用 @ 语法糖

def my_decorator(func):
    def wrapper():
        print("=" * 20)
        func()
        print("=" * 20)
    return wrapper

@my_decorator  # 等价于 say_hello = my_decorator(say_hello)
def say_hello():
    print("你好!")

say_hello()

输出:

====================
你好!
====================

2. 带参数的被装饰函数

2.1 使用 *args, **kwargs

def log_decorator(func):
    """记录函数调用的装饰器"""
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__},参数: {args}, {kwargs}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} 返回: {result}")
        return result
    return wrapper

@log_decorator
def add(a, b):
    return a + b

@log_decorator
def greet(name, greeting="你好"):
    return f"{greeting}, {name}!"

# 测试
add(3, 5)
greet("小明", greeting="早上好")

2.2 保持函数元信息

from functools import wraps

def proper_decorator(func):
    """正确的装饰器写法"""
    @wraps(func)  # 保留原函数的元信息
    def wrapper(*args, **kwargs):
        """包装函数"""
        return func(*args, **kwargs)
    return wrapper

@proper_decorator
def example():
    """示例函数"""
    pass

# 对比有无 @wraps
print(f"函数名: {example.__name__}")      # example(有 @wraps)
print(f"文档: {example.__doc__}")          # 示例函数

3. 带参数的装饰器

3.1 三层嵌套装饰器

def repeat(num):
    """重复执行 num 次的装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for i in range(num):
                print(f"第 {i + 1} 次执行:")
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def say_hi():
    print("嗨!")

say_hi()

输出:

第 1 次执行:
嗨!
第 2 次执行:
嗨!
第 3 次执行:
嗨!

3.2 计时装饰器

import time
from functools import wraps

def timer(prefix=""):
    """计时装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)
            elapsed = time.time() - start
            print(f"{prefix}[{func.__name__}] 耗时: {elapsed:.4f} 秒")
            return result
        return wrapper
    return decorator

@timer("[性能] ")
def slow_function():
    time.sleep(0.1)
    return "完成"

slow_function()

4. 常用装饰器示例

4.1 缓存装饰器

def memoize(func):
    """简单缓存装饰器"""
    cache = {}
    
    @wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
            print(f"缓存新结果: {args}")
        else:
            print(f"使用缓存: {args}")
        return cache[args]
    
    wrapper.cache = cache  # 暴露缓存
    return wrapper

@memoize
def fibonacci(n):
    """斐波那契数列"""
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(5))
print(fibonacci(5))  # 使用缓存
print(f"缓存内容: {fibonacci.cache}")

4.2 权限检查装饰器

def require_login(func):
    """要求登录的装饰器"""
    @wraps(func)
    def wrapper(user, *args, **kwargs):
        if not user.get("is_logged_in"):
            return "请先登录"
        return func(user, *args, **kwargs)
    return wrapper

def require_admin(func):
    """要求管理员权限"""
    @wraps(func)
    def wrapper(user, *args, **kwargs):
        if user.get("role") != "admin":
            return "需要管理员权限"
        return func(user, *args, **kwargs)
    return wrapper

# 使用
@require_login
@require_admin
def delete_user(user, user_id):
    return f"已删除用户 {user_id}"

# 测试
admin = {"name": "管理员", "is_logged_in": True, "role": "admin"}
guest = {"name": "访客", "is_logged_in": False}

print(delete_user(admin, 123))
print(delete_user(guest, 123))

4.3 重试装饰器

import time
from functools import wraps

def retry(max_attempts=3, delay=1):
    """失败重试装饰器"""
    def decorator(func):
        @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 None
        return wrapper
    return decorator

attempt_count = 0

@retry(max_attempts=3, delay=0.5)
def unstable_function():
    """不稳定的函数"""
    global attempt_count
    attempt_count += 1
    if attempt_count < 3:
        raise ValueError("模拟错误")
    return "成功!"

print(unstable_function())

5. 类装饰器

5.1 使用函数作为类装饰器

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("初始化数据库连接")
    
    def query(self, sql):
        return f"执行: {sql}"

# 测试
db1 = Database()
db2 = Database()
print(db1 is db2)  # True

5.2 使用类作为装饰器

class CountCalls:
    """统计函数调用次数的类装饰器"""
    
    def __init__(self, func):
        from functools import update_wrapper
        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 process_data():
    print("处理数据...")

process_data()
process_data()
process_data()

6. 多个装饰器的叠加

def decorator_a(func):
    print("装饰器 A 应用")
    def wrapper(*args, **kwargs):
        print("A - 之前")
        result = func(*args, **kwargs)
        print("A - 之后")
        return result
    return wrapper

def decorator_b(func):
    print("装饰器 B 应用")
    def wrapper(*args, **kwargs):
        print("B - 之前")
        result = func(*args, **kwargs)
        print("B - 之后")
        return result
    return wrapper

@decorator_a
@decorator_b
def my_function():
    print("函数执行")

print("\n--- 调用函数 ---")
my_function()

输出:

装饰器 B 应用
装饰器 A 应用

--- 调用函数 ---
A - 之前
B - 之前
函数执行
B - 之后
A - 之后

执行顺序:从内到外应用(bottom-up),从外到内执行(top-down)。


7. 实际应用:方法装饰器

class Calculator:
    """计算器类"""
    
    def __init__(self):
        self.history = []
    
    def _log_operation(func):
        """记录操作的装饰器"""
        def wrapper(self, *args):
            result = func(self, *args)
            self.history.append(f"{func.__name__}{args} = {result}")
            return result
        return wrapper
    
    @_log_operation
    def add(self, a, b):
        return a + b
    
    @_log_operation
    def multiply(self, a, b):
        return a * b
    
    def get_history(self):
        return self.history

calc = Calculator()
calc.add(2, 3)
calc.multiply(4, 5)
print(calc.get_history())
# ['add(2, 3) = 5', 'multiply(4, 5) = 20']

本节小结

  • 装饰器:接收函数作为参数并返回新函数的高阶函数
  • @ 语法糖@decorator 等价于 func = decorator(func)
  • @wraps:使用 functools.wraps 保留原函数元信息
  • 带参数的装饰器:需要三层嵌套函数
  • 类装饰器:通过 __init____call__ 实现
  • 多个装饰器:应用顺序从内到外,执行顺序从外到内

练习

  1. 编写一个装饰器,限制函数的执行时间,超时抛出异常
  2. 实现一个装饰器,自动将函数的字符串结果转换为大写
  3. 创建一个类装饰器,为类中的所有方法添加日志功能
  4. 编写参数化装饰器 @validate_type(int, str),检查参数类型