折腾侠
技术教程

Python 装饰器深入理解:从原理到实战应用

折腾侠
2026/03/26 发布
11约 9 分钟1474 字 / 967 词00

Python 装饰器深入理解:从原理到实战应用

引言

装饰器(Decorator)是 Python 中最强大也最令人困惑的特性之一。许多开发者在日常工作中会使用 INLINE_CODE_0INLINE_CODE_1 或 Web 框架中的 INLINE_CODE_2,但对其背后的原理和自定义装饰器的编写却知之甚少。本文将深入探讨装饰器的本质,并通过实际案例展示如何编写实用的装饰器。

一、装饰器的本质是什么?

1.1 函数即对象

要理解装饰器,首先要理解 Python 中"函数是一等公民"的概念。函数可以像其他对象一样被传递、赋值和返回:

Python
def greet(name):
    return f"Hello, {name}!"

# 函数可以赋值给变量
say_hello = greet
print(say_hello("Alice"))  # 输出:Hello, Alice!

# 函数可以作为参数传递
def call_func(func, arg):
    return func(arg)

print(call_func(greet, "Bob"))  # 输出:Hello, Bob!

# 函数可以作为返回值
def create_greeter(prefix):
    def greeter(name):
        return f"{prefix}, {name}!"
    return greeter

say_hi = create_greeter("Hi")
print(say_hi("Charlie"))  # 输出:Hi, Charlie!

1.2 装饰器的基本形式

装饰器本质上是一个接受函数作为参数并返回新函数的可调用对象。最简单的装饰器如下:

Python
def simple_decorator(func):
    def wrapper(*args, **kwargs):
        print("函数执行前")
        result = func(*args, **kwargs)
        print("函数执行后")
        return result
    return wrapper

@simple_decorator
def say_name(name):
    print(f"名字是:{name}")

say_name("测试")
# 输出:
# 函数执行前
# 名字是:测试
# 函数执行后

INLINE_CODE_3 语法等价于 INLINE_CODE_4

二、编写实用的装饰器

2.1 保留函数元信息

上面的简单装饰器有一个问题:被装饰函数的 INLINE_CODE_5INLINE_CODE_6 等元信息会丢失。使用 INLINE_CODE_7 可以解决这个问题:

Python
from functools import wraps

def preserved_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        """包装函数的文档"""
        return func(*args, **kwargs)
    return wrapper

@preserved_decorator
def documented_function():
    """这是原始函数的文档"""
    pass

print(documented_function.__name__)  # 输出:documented_function
print(documented_function.__doc__)   # 输出:这是原始函数的文档

2.2 带参数的装饰器

实际应用中,我们经常需要给装饰器传递参数。这需要三层嵌套:

Python
def repeat_decorator(times):
    """重复执行函数指定次数"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            results = []
            for i in range(times):
                print(f"第 {i + 1} 次执行")
                results.append(func(*args, **kwargs))
            return results
        return wrapper
    return decorator

@repeat_decorator(3)
def get_random_number():
    import random
    return random.randint(1, 100)

print(get_random_number())  # 输出包含 3 个随机数的列表

2.3 类装饰器

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

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)

@CountCalls
def say_hello(name):
    print(f"Hello, {name}!")

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

三、装饰器的实际应用场景

3.1 性能监控装饰器

在生产环境中,我们经常需要监控函数的执行时间:

Python
import time
from functools import wraps

def timing_decorator(func):
    @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_function():
    time.sleep(1)
    return "完成"

slow_function()  # 输出:slow_function 执行时间:1.00xxxx 秒

3.2 重试机制装饰器

网络请求或文件操作可能因临时故障失败,重试机制可以提高程序的健壮性:

Python
import random
import time
from functools import wraps

def retry_decorator(max_attempts=3, delay=1, exceptions=(Exception,)):
    """
    重试装饰器
    
    参数:
        max_attempts: 最大重试次数
        delay: 每次重试的等待时间(秒)
        exceptions: 需要捕获的异常类型
    """
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            last_exception = None
            for attempt in range(max_attempts):
                try:
                    return func(*args, **kwargs)
                except exceptions 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_decorator(max_attempts=3, delay=0.5)
def unstable_operation():
    if random.random() < 0.7:  # 70% 概率失败
        raise ConnectionError("网络连接失败")
    return "操作成功"

try:
    result = unstable_operation()
    print(result)
except Exception as e:
    print(f"最终失败:{e}")

3.3 缓存装饰器(Memoization)

对于计算密集型且结果确定的函数,缓存可以显著提升性能:

Python
from functools import wraps

def memoize_decorator(func):
    """简单的缓存装饰器"""
    cache = {}
    
    @wraps(func)
    def wrapper(*args):
        if args not in cache:
            print(f"计算 {args} 的结果...")
            cache[args] = func(*args)
        else:
            print(f"从缓存获取 {args} 的结果")
        return cache[args]
    return wrapper

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

print(fibonacci(10))  # 第一次计算
print(fibonacci(10))  # 从缓存获取
print(fibonacci(11))  # 只需计算新部分

3.4 权限验证装饰器

在 Web 应用中,装饰器常用于权限控制:

Python
from functools import wraps

def require_permission(required_role):
    """权限验证装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(user, *args, **kwargs):
            if user.get('role') != required_role:
                raise PermissionError(
                    f"需要 {required_role} 权限,当前权限:{user.get('role')}"
                )
            print(f"权限验证通过:{user.get('name')}")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

@require_permission('admin')
def delete_user(user, target_id):
    print(f"管理员 {user['name']} 删除了用户 {target_id}")

@require_permission('user')
def view_profile(user, target_id):
    print(f"用户 {user['name']} 查看了用户 {target_id} 的资料")

# 测试
admin_user = {'name': '张三', 'role': 'admin'}
regular_user = {'name': '李四', 'role': 'user'}

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

3.5 日志记录装饰器

日志是调试和监控的重要工具:

Python
import logging
from functools import wraps
from datetime import datetime

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

def log_decorator(log_level=logging.INFO):
    """日志记录装饰器"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            logging.log(log_level, 
                       f"调用 {func.__name__}, 参数:args={args}, kwargs={kwargs}")
            try:
                result = func(*args, **kwargs)
                logging.log(log_level, 
                           f"{func.__name__} 返回:{result}")
                return result
            except Exception as e:
                logging.error(f"{func.__name__} 抛出异常:{e}")
                raise
        return wrapper
    return decorator

@log_decorator()
def divide(a, b):
    return a / b

divide(10, 2)  # 会记录调用和返回
# divide(10, 0)  # 会记录异常

四、装饰器的高级技巧

4.1 装饰器链

多个装饰器可以叠加使用,执行顺序是从下往上:

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 test_func():
    print("执行函数")

test_func()
# 输出顺序:
# [A] 前处理
# [B] 前处理
# 执行函数
# [B] 后处理
# [A] 后处理

4.2 类方法装饰器

装饰器也可以用于类方法,需要注意 INLINE_CODE_9 参数:

Python
def method_logger(func):
    @wraps(func)
    def wrapper(self, *args, **kwargs):
        print(f"调用方法:{func.__name__},对象:{self.__class__.__name__}")
        return func(self, *args, **kwargs)
    return wrapper

class Calculator:
    def __init__(self, value=0):
        self.value = value
    
    @method_logger
    def add(self, x):
        self.value += x
        return self.value
    
    @method_logger
    def multiply(self, x):
        self.value *= x
        return self.value

calc = Calculator(10)
print(calc.add(5))      # 调用方法:add,对象:Calculator
print(calc.multiply(3)) # 调用方法:multiply,对象:Calculator

4.3 使用装饰器实现单例模式

Python
def singleton_decorator(cls):
    """单例模式装饰器"""
    instances = {}
    
    @wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    
    return get_instance

@singleton_decorator
class DatabaseConnection:
    def __init__(self):
        print("创建数据库连接")
        self.connected = True

db1 = DatabaseConnection()  # 创建数据库连接
db2 = DatabaseConnection()  # 不会再次创建
print(db1 is db2)  # 输出:True

五、最佳实践与注意事项

5.1 始终使用 @wraps

忘记使用 INLINE_CODE_10 是常见错误,会导致调试困难:

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

5.2 装饰器应该透明

好的装饰器不应该改变原函数的行为(除了附加功能):

Python
# 透明装饰器 - 推荐
def transparent_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("日志:函数被调用")
        return func(*args, **kwargs)  # 原样返回
    return wrapper

# 不透明装饰器 - 避免
def opaque_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return str(result)  # 改变了返回类型!
    return wrapper

5.3 处理异步函数

对于异步函数,需要使用专门的装饰器:

Python
import asyncio
from functools import wraps

def async_timing_decorator(func):
    @wraps(func)
    async def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = await func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} 执行时间:{elapsed:.6f} 秒")
        return result
    return wrapper

@async_timing_decorator
async def async_fetch():
    await asyncio.sleep(1)
    return "数据"

# asyncio.run(async_fetch())

六、总结

装饰器是 Python 中最强大的特性之一,它允许我们以声明式的方式增强函数功能。通过本文的学习,你应该掌握了:

  1. 装饰器的本质:接受函数并返回新函数的可调用对象
  2. 基础写法:使用 INLINE_CODE_11 保留元信息
  3. 参数化装饰器:三层嵌套实现可配置装饰器
  4. 类装饰器:通过 INLINE_CODE_12 实现状态保持
  5. 实际应用:性能监控、重试、缓存、权限、日志等
  6. 高级技巧:装饰器链、类方法装饰、单例模式
  7. 最佳实践:保持透明、处理异步函数

装饰器的学习曲线较陡,但一旦掌握,你将能够编写更加优雅、可维护的 Python 代码。建议从简单的日志装饰器开始练习,逐步尝试更复杂的场景。

课后练习

  1. 编写一个装饰器,限制函数的最大执行时间,超时则抛出异常
  2. 实现一个缓存装饰器,支持缓存过期时间
  3. 创建一个装饰器,统计函数的调用频率(每分钟/小时/天)
  4. 设计一个装饰器,自动将函数的返回值转换为 JSON 格式

通过实践这些练习,你将更深入地理解装饰器的强大之处。


本文示例代码均在 Python 3.8+ 环境下测试通过。装饰器是 Python 进阶的必备技能,建议在真实项目中逐步应用。

分享到:

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

加载评论中...