折腾侠
技术教程

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

折腾侠
2026/04/26 发布
0约 8 分钟1287 字 / 913 词00

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

引言

在 Python 编程中,装饰器(Decorator)是最强大也最优雅的特性之一。它允许我们在不修改原函数代码的前提下,动态地为函数添加功能。无论是 Web 框架中的路由定义、API 接口的权限验证,还是性能监控和日志记录,装饰器都在幕后发挥着关键作用。

本文将深入讲解 Python 装饰器的核心概念、工作原理和实际应用场景,帮助你真正掌握这一利器。

一、装饰器的基本概念

什么是装饰器?

装饰器本质上是一个高阶函数,它接受一个函数作为参数,并返回一个新的函数。使用装饰器可以在不改变原函数定义的情况下,为其添加额外的功能。

装饰器的语法使用 INLINE_CODE_0 符号,放在函数定义的上方:

Python
@decorator_name
def my_function():
    pass

这等价于:

Python
def my_function():
    pass
my_function = decorator_name(my_function)

为什么需要装饰器?

想象一下,你有 100 个函数都需要记录执行时间。如果没有装饰器,你需要在每个函数内部添加计时代码。使用装饰器,你只需定义一次,然后应用到所有需要的函数上。这体现了 Python 的 DRY(Don't Repeat Yourself)原则。

二、基础装饰器示例

示例 1:简单的日志装饰器

Python
import functools
from datetime import datetime

def log_execution(func):
    """记录函数执行的装饰器"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"[{datetime.now()}] 开始执行:{func.__name__}")
        result = func(*args, **kwargs)
        print(f"[{datetime.now()}] 执行完成:{func.__name__}")
        return result
    return wrapper

@log_execution
def greet(name):
    """问候函数"""
    print(f"你好,{name}!")
    return f"Hello, {name}!"

# 使用
greet("张三")

输出:

[2026-04-26 11:00:00.123456] 开始执行:greet
你好,张三!
[2026-04-26 11:00:00.123789] 执行完成:greet

示例 2:性能监控装饰器

Python
import time
import functools

def timing_decorator(func):
    """计算函数执行时间的装饰器"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        elapsed = end_time - start_time
        print(f"{func.__name__} 执行耗时:{elapsed:.6f} 秒")
        return result
    return wrapper

@timing_decorator
def slow_operation():
    """模拟耗时操作"""
    time.sleep(0.5)
    return "完成"

slow_operation()

三、带参数的装饰器

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

示例 3:可配置的重试装饰器

Python
import functools
import time

def retry(max_attempts=3, delay=1):
    """
    重试装饰器
    :param max_attempts: 最大重试次数
    :param delay: 重试间隔(秒)
    """
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            last_exception = None
            for attempt in range(max_attempts):
                try:
                    print(f"尝试第 {attempt + 1} 次执行 {func.__name__}")
                    return func(*args, **kwargs)
                except Exception as e:
                    last_exception = e
                    print(f"第 {attempt + 1} 次失败:{e}")
                    if attempt < max_attempts - 1:
                        time.sleep(delay)
            raise last_exception
        return wrapper
    return decorator

@retry(max_attempts=3, delay=0.5)
def unstable_api():
    """模拟不稳定的 API 调用"""
    import random
    if random.random() < 0.7:
        raise ConnectionError("网络连接失败")
    return "API 调用成功"

# 使用
try:
    result = unstable_api()
    print(result)
except Exception as e:
    print(f"最终失败:{e}")

示例 4:权限验证装饰器

Python
from functools import wraps

def require_role(required_role):
    """
    角色权限验证装饰器
    :param required_role: 需要的角色级别
    """
    def decorator(func):
        @wraps(func)
        def wrapper(user, *args, **kwargs):
            if user.get('role') != required_role:
                raise PermissionError(
                    f"权限不足:需要 {required_role} 角色,"
                    f"当前为 {user.get('role')}"
                )
            print(f"用户 {user.get('name')} 通过 {required_role} 验证")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

@require_role('admin')
def delete_user(user, target_user_id):
    """删除用户(仅管理员可执行)"""
    print(f"管理员 {user['name']} 删除了用户 {target_user_id}")
    return True

# 测试
admin_user = {'name': '管理员', 'role': 'admin'}
normal_user = {'name': '普通用户', 'role': 'user'}

delete_user(admin_user, 123)  # 成功
# delete_user(normal_user, 123)  # 抛出 PermissionError

四、类装饰器

装饰器不仅可以是函数,也可以是类。类装饰器通过实现 INLINE_CODE_1 方法来工作。

示例 5:类实现的计数装饰器

Python
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():
    print("Hello!")

say_hello()
say_hello()
say_hello()

输出:

say_hello 已被调用 1 次
Hello!
say_hello 已被调用 2 次
Hello!
say_hello 已被调用 3 次
Hello!

五、实际应用场景

场景 1:Web 框架中的路由装饰器

Flask 等 Web 框架广泛使用装饰器定义路由:

Python
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return '首页'

@app.route('/user/<username>')
def user_profile(username):
    return f'用户:{username}'

@app.route('/api/data', methods=['POST'])
def api_data():
    return {'status': 'success'}

场景 2:API 速率限制

Python
import time
from collections import defaultdict
from functools import wraps

def rate_limit(max_calls, period):
    """
    API 速率限制装饰器
    :param max_calls: 允许的最大调用次数
    :param period: 时间窗口(秒)
    """
    calls = defaultdict(list)
    
    def decorator(func):
        @wraps(func)
        def wrapper(client_id, *args, **kwargs):
            now = time.time()
            # 清理过期记录
            calls[client_id] = [
                t for t in calls[client_id] 
                if now - t < period
            ]
            
            if len(calls[client_id]) >= max_calls:
                raise Exception("请求频率超限")
            
            calls[client_id].append(now)
            return func(client_id, *args, **kwargs)
        return wrapper
    return decorator

@rate_limit(max_calls=5, period=60)
def api_request(client_id, data):
    return f"处理 {client_id} 的请求:{data}"

场景 3:缓存装饰器(记忆化)

Python
import functools

def memoize(func):
    """缓存函数结果的装饰器"""
    cache = {}
    
    @functools.wraps(func)
    def wrapper(*args):
        if args not in cache:
            print(f"计算 {func.__name__}{args}")
            cache[args] = func(*args)
        else:
            print(f"缓存命中 {func.__name__}{args}")
        return cache[args]
    return wrapper

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

fibonacci(5)
fibonacci(5)  # 缓存命中

场景 4:事务管理装饰器

Python
from contextlib import contextmanager

def transaction(func):
    """数据库事务装饰器"""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print("开始事务")
        try:
            result = func(*args, **kwargs)
            print("提交事务")
            return result
        except Exception as e:
            print(f"回滚事务:{e}")
            raise
    return wrapper

@transaction
def transfer_money(from_account, to_account, amount):
    """转账操作"""
    print(f"从 {from_account} 转账 {amount}{to_account}")
    return True

六、最佳实践和注意事项

1. 使用 functools.wraps

始终使用 INLINE_CODE_2 保留原函数的元数据(名称、文档字符串等):

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

# 正确做法
def good_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

2. 装饰器顺序

多个装饰器时,执行顺序是从下往上,从外往内:

Python
@decorator_a
@decorator_b
@decorator_c
def my_func():
    pass
# 等价于:decorator_a(decorator_b(decorator_c(my_func)))

3. 保持装饰器通用性

设计装饰器时尽量使其通用,可以应用于多种函数:

Python
# 好的设计:接受任意参数
def universal_decorator(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

4. 避免过度使用

装饰器虽然强大,但不要滥用。如果逻辑过于复杂,考虑使用其他设计模式。

七、综合实战:构建一个完整的装饰器库

Python
import time
import logging
import functools
from typing import Callable, Any

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

class DecoratorLibrary:
    """装饰器工具库"""
    
    @staticmethod
    def log_args(func: Callable) -> Callable:
        """记录函数参数的装饰器"""
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            logger.info(f"{func.__name__} 参数:args={args}, kwargs={kwargs}")
            return func(*args, **kwargs)
        return wrapper
    
    @staticmethod
    def cache_result(expire_seconds: int = 300):
        """带过期时间的缓存装饰器"""
        cache = {}
        timestamps = {}
        
        def decorator(func: Callable) -> Callable:
            @functools.wraps(func)
            def wrapper(*args) -> Any:
                now = time.time()
                key = str(args)
                
                # 检查缓存是否过期
                if key in cache:
                    if now - timestamps[key] < expire_seconds:
                        logger.info(f"缓存命中:{func.__name__}")
                        return cache[key]
                    else:
                        del cache[key]
                        del timestamps[key]
                
                # 计算并缓存
                logger.info(f"计算:{func.__name__}")
                result = func(*args)
                cache[key] = result
                timestamps[key] = now
                return result
            return wrapper
        return decorator
    
    @staticmethod
    def validate_types(**expected_types):
        """参数类型验证装饰器"""
        def decorator(func: Callable) -> Callable:
            @functools.wraps(func)
            def wrapper(*args, **kwargs):
                import inspect
                sig = inspect.signature(func)
                bound = sig.bind(*args, **kwargs)
                bound.apply_defaults()
                
                for param_name, expected_type in expected_types.items():
                    if param_name in bound.arguments:
                        value = bound.arguments[param_name]
                        if not isinstance(value, expected_type):
                            raise TypeError(
                                f"{param_name} 应该是 {expected_type.__name__}, "
                                f"实际是 {type(value).__name__}"
                            )
                return func(*args, **kwargs)
            return wrapper
        return decorator

# 使用示例
@DecoratorLibrary.log_args
@DecoratorLibrary.validate_types(name=str, age=int)
@DecoratorLibrary.cache_result(expire_seconds=60)
def create_user(name: str, age: int) -> dict:
    """创建用户"""
    time.sleep(0.1)  # 模拟耗时操作
    return {"name": name, "age": age, "id": hash(name) % 10000}

# 测试
user1 = create_user("张三", 25)
user2 = create_user("张三", 25)  # 缓存命中
print(user1)

结语

装饰器是 Python 中最优雅的特性之一,它让我们能够以声明式的方式为函数添加功能,大大提高了代码的可复用性和可维护性。

通过本文的学习,你应该已经掌握了:

  • 装饰器的基本概念和工作原理
  • 如何编写基础装饰器和带参数的装饰器
  • 类装饰器的实现方式
  • 装饰器在实际项目中的多种应用场景
  • 编写装饰器的最佳实践

记住,装饰器虽然强大,但也要适度使用。当装饰器逻辑过于复杂时,考虑是否应该重构代码结构。好的装饰器应该是简洁、通用且易于理解的。

现在,开始在你的项目中使用装饰器吧,让代码更加优雅和高效!

分享到:

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

加载评论中...