折腾侠
技术教程

Python 装饰器完全指南:从入门到实战应用

折腾侠
2026/04/22 发布
0约 8 分钟1428 字 / 849 词00

Python 装饰器完全指南:从入门到实战应用

引言

装饰器(Decorator)是 Python 中最强大也最令人困惑的特性之一。它允许你在不修改原函数代码的情况下,动态地增强或修改函数的行为。本文将带你从基础概念到高级应用,全面掌握 Python 装饰器的使用技巧。

很多初学者第一次看到 INLINE_CODE_0 语法时都会感到困惑:这是什么魔法?为什么函数上面要加个 INLINE_CODE_1 符号?实际上,装饰器的本质非常简单——它就是一个接收函数作为参数并返回新函数的高阶函数。

一、装饰器的基础概念

1.1 什么是装饰器?

装饰器本质上是一个 callable 对象(函数或类),它接受一个函数作为输入,并返回一个新的函数。这个新模式通常会在原函数的基础上添加一些额外的功能。

用数学语言来说,如果 INLINE_CODE_2 是原函数,INLINE_CODE_3 是装饰器,那么:

decorated_f = decorator(f)

INLINE_CODE_4 语法糖等价于:

Python
@decorator
def f():
    pass

# 等价于
def f():
    pass
f = decorator(f)

1.2 第一个装饰器示例

让我们从一个最简单的例子开始:

Python
def simple_decorator(func):
    """最简单的装饰器"""
    def wrapper(*args, **kwargs):
        print("函数执行前")
        result = func(*args, **kwargs)
        print("函数执行后")
        return result
    return wrapper

@simple_decorator
def greet(name):
    print(f"你好,{name}!")
    return f"问候完成"

# 调用
result = greet("张三")
# 输出:
# 函数执行前
# 你好,张三!
# 函数执行后
# result = "问候完成"

这个例子展示了装饰器的基本结构:

  1. 外层函数接收原函数作为参数
  2. 内层 wrapper 函数接收原函数的参数
  3. 在调用原函数前后添加额外逻辑
  4. 返回 wrapper 函数

二、装饰器的核心原理

2.1 函数是一等公民

Python 中函数是一等公民(first-class citizen),这意味着:

  • 函数可以作为参数传递给其他函数
  • 函数可以作为返回值从函数中返回
  • 函数可以赋值给变量

装饰器正是利用了这一特性。

2.2 闭包的作用

装饰器中的 wrapper 函数使用了闭包(closure)来访问外层函数的变量。让我们深入理解:

Python
def counter_decorator(func):
    count = 0  # 这个变量会被闭包捕获
    
    def wrapper(*args, **kwargs):
        nonlocal count
        count += 1
        print(f"{func.__name__} 已被调用 {count} 次")
        return func(*args, **kwargs)
    
    return wrapper

@counter_decorator
def say_hello():
    print("Hello!")

say_hello()  # 已被调用 1 次
say_hello()  # 已被调用 2 次
say_hello()  # 已被调用 3 次

INLINE_CODE_5 关键字允许我们在内层函数中修改外层函数的变量。

三、保留函数元信息

3.1 functools.wraps 的重要性

使用装饰器后,原函数的元信息(如 INLINE_CODE_6INLINE_CODE_7)会丢失:

Python
def my_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def original_function():
    """这是原文档字符串"""
    pass

print(original_function.__name__)  # 输出:wrapper(错误!)
print(original_function.__doc__)   # 输出:None(错误!)

使用 INLINE_CODE_8 可以保留原函数的元信息:

Python
from functools import wraps

def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@my_decorator
def original_function():
    """这是原文档字符串"""
    pass

print(original_function.__name__)  # 输出:original_function ✓
print(original_function.__doc__)   # 输出:这是原文档字符串 ✓

最佳实践:始终使用 INLINE_CODE_9 装饰你的 wrapper 函数!

四、带参数的装饰器

4.1 装饰器工厂模式

有时我们需要给装饰器本身传递参数,这需要三层嵌套:

Python
def repeat(times):
    """带参数的装饰器工厂"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def greet(name):
    print(f"你好,{name}!")

greet("李四")
# 输出三次:你好,李四!

结构解析:

  1. 最外层 INLINE_CODE_10 接收装饰器参数
  2. 中间层 INLINE_CODE_11 接收被装饰的函数
  3. 最内层 INLINE_CODE_12 接收函数调用参数

4.2 实际应用:重试机制

Python
import time
from functools import wraps

def retry(max_attempts=3, delay=1):
    """失败重试装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            last_exception = None
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    last_exception = e
                    print(f"尝试 {attempt + 1}/{max_attempts} 失败:{e}")
                    if attempt < max_attempts - 1:
                        time.sleep(delay)
            raise last_exception
        return wrapper
    return decorator

@retry(max_attempts=3, delay=2)
def fetch_data(url):
    # 模拟可能失败的网络请求
    import random
    if random.random() < 0.7:
        raise ConnectionError("网络错误")
    return "数据获取成功"

五、类装饰器

5.1 用类实现装饰器

装饰器也可以是类,通过实现 INLINE_CODE_13 方法:

Python
class CountCalls:
    """统计函数调用次数的类装饰器"""
    
    def __init__(self, func):
        wraps(func)(self)
        self.func = func
        self.count = 0
    
    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"调用次数:{self.count}")
        return self.func(*args, **kwargs)
    
    def reset_count(self):
        self.count = 0

@CountCalls
def say_hello():
    print("Hello!")

say_hello()  # 调用次数:1
say_hello()  # 调用次数:2
print(f"总调用:{say_hello.count}次")  # 总调用:2 次

5.2 带参数的类装饰器

Python
class RateLimiter:
    """速率限制装饰器"""
    
    def __init__(self, max_calls, period):
        self.max_calls = max_calls
        self.period = period
        self.calls = []
    
    def __call__(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            now = time.time()
            # 移除超出时间窗口的调用记录
            self.calls = [t for t in self.calls if now - t < self.period]
            
            if len(self.calls) >= self.max_calls:
                raise Exception(f"速率限制:{self.max_calls} 次/{self.period}秒")
            
            self.calls.append(now)
            return func(*args, **kwargs)
        return wrapper

@RateLimiter(max_calls=5, period=10)
def api_call():
    return "API 响应"

六、实际应用场景

6.1 日志记录

Python
def log_calls(func):
    """记录函数调用的装饰器"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        import logging
        logging.info(f"调用 {func.__name__}, 参数:args={args}, kwargs={kwargs}")
        try:
            result = func(*args, **kwargs)
            logging.info(f"{func.__name__} 返回:{result}")
            return result
        except Exception as e:
            logging.error(f"{func.__name__} 抛出异常:{e}")
            raise
    return wrapper

6.2 性能分析

Python
def timing(func):
    """测量函数执行时间的装饰器"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        import time
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        print(f"{func.__name__} 执行时间:{end - start:.4f} 秒")
        return result
    return wrapper

@timing
def slow_function():
    time.sleep(1)
    return "完成"

6.3 权限验证

Python
def require_auth(func):
    """权限验证装饰器"""
    @wraps(func)
    def wrapper(user, *args, **kwargs):
        if not user.is_authenticated:
            raise PermissionError("用户未认证")
        if not user.has_permission(func.__name__):
            raise PermissionError("用户无权限")
        return func(user, *args, **kwargs)
    return wrapper

@require_auth
def delete_resource(user, resource_id):
    # 只有认证且有权限的用户才能执行
    pass

6.4 缓存(记忆化)

Python
def memoize(func):
    """缓存函数结果的装饰器"""
    cache = {}
    
    @wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrapper

@memoize
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(100))  # 瞬间完成,无需重复计算

6.5 事务管理

Python
def transaction(func):
    """数据库事务装饰器"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        db.begin_transaction()
        try:
            result = func(*args, **kwargs)
            db.commit()
            return result
        except Exception as e:
            db.rollback()
            raise
    return wrapper

@transaction
def transfer_money(from_account, to_account, amount):
    # 要么全部成功,要么全部回滚
    pass

七、装饰器栈(多个装饰器)

一个函数可以应用多个装饰器,从下往上执行:

Python
def decorator_a(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("装饰器 A - 前")
        result = func(*args, **kwargs)
        print("装饰器 A - 后")
        return result
    return wrapper

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

@decorator_a
@decorator_b
def hello():
    print("Hello!")

hello()
# 输出顺序:
# 装饰器 A - 前
# 装饰器 B - 前
# Hello!
# 装饰器 B - 后
# 装饰器 A - 后

八、常见陷阱与最佳实践

8.1 陷阱:忘记使用 @wraps

Python
# 错误示范
def bad_decorator(func):
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

# 正确示范
def good_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

8.2 陷阱:装饰器修改可变参数

Python
def dangerous_decorator(func):
    @wraps(func)
    def wrapper(items, *args, **kwargs):
        items.append("modified")  # 修改了传入的可变对象!
        return func(items, *args, **kwargs)
    return wrapper

解决方案:复制可变参数后再修改

8.3 最佳实践清单

  1. ✅ 始终使用 INLINE_CODE_14 保留元信息
  2. ✅ 使用 INLINE_CODE_15 保证通用性
  3. ✅ 装饰器函数命名清晰,说明用途
  4. ✅ 避免在装饰器中修改可变参数
  5. ✅ 复杂逻辑考虑用类装饰器
  6. ✅ 为装饰器编写文档字符串
  7. ✅ 单元测试覆盖装饰器边界情况

结语

装饰器是 Python 中最优雅的特性之一,它让代码更加模块化、可复用和易维护。从简单的日志记录到复杂的权限控制,装饰器的应用场景无处不在。

掌握装饰器的关键在于理解:

  1. 函数是一等公民,可以作为参数和返回值
  2. 闭包让内层函数可以访问外层变量
  3. INLINE_CODE_16 保留原函数元信息
  4. 装饰器工厂模式支持参数化
  5. 类装饰器适合需要状态的场景

希望本教程能帮助你真正理解并熟练运用 Python 装饰器。记住,最好的学习方式是实践——尝试为你的项目编写一些实用的装饰器吧!


参考资料

  • Python 官方文档:Decorators
  • 《Fluent Python》第 7 章
  • Real Python: Decorators in Python
分享到:

如果这篇文章对你有帮助,欢迎请作者喝杯咖啡 ☕

加载评论中...