折腾侠
技术教程

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

折腾侠
2026/04/21 发布
0约 8 分钟1558 字 / 729 词00

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

引言

在 Python 编程的世界里,装饰器(Decorator)是一个既强大又优雅的特性。它允许我们在不修改原有代码的前提下,为函数或类添加新的功能。这种"开放 - 封闭原则"的实现方式,使得代码更加模块化、可复用,也更易于维护。

无论你是刚接触 Python 的新手,还是已经有一定经验的开发者,深入理解装饰器都将为你的编程技能带来质的飞跃。本文将带你从装饰器的基础概念出发,逐步深入到高级用法和实际应用场景,最终掌握这一 Python 编程的利器。

一、装饰器的基本概念

1.1 什么是装饰器?

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

想象一下,你有一个精美的相框(原函数),装饰器就像是在这个相框外面再套上一个更华丽的边框,而不需要改动相框本身。

1.2 为什么需要装饰器?

在实际开发中,我们经常遇到以下场景:

  • 日志记录:需要在函数执行前后记录日志
  • 性能测试:需要测量函数的执行时间
  • 权限验证:需要在函数执行前检查用户权限
  • 缓存优化:需要缓存函数的返回值以提高性能
  • 事务管理:需要在函数执行前后处理数据库事务

如果没有装饰器,我们可能需要在每个函数中重复编写相同的代码。而装饰器让我们能够将这些通用逻辑提取出来,实现代码的复用。

二、函数装饰器详解

2.1 最简单的装饰器

让我们从一个最简单的装饰器开始:

Python
def simple_decorator(func):
    def wrapper():
        print("函数执行前")
        func()
        print("函数执行后")
    return wrapper

@simple_decorator
def say_hello():
    print("Hello, World!")

say_hello()

输出结果:

函数执行前
Hello, World!
函数执行后

这里的 INLINE_CODE_0 语法糖等价于 INLINE_CODE_1

2.2 保留原函数信息

上面的例子有一个问题:装饰后的函数会丢失原有的名称和文档字符串。为了解决这个问题,我们可以使用 INLINE_CODE_2

Python
from functools import wraps

def decorator_with_wraps(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("执行前准备")
        result = func(*args, **kwargs)
        print("执行后清理")
        return result
    return wrapper

@decorator_with_wraps
def greet(name, greeting="Hello"):
    """向用户打招呼"""
    print(f"{greeting}, {name}!")
    return name

print(greet.__name__)  # 输出:greet
print(greet.__doc__)   # 输出:向用户打招呼

使用 INLINE_CODE_3 可以确保装饰后的函数保留原有的 INLINE_CODE_4INLINE_CODE_5 等属性。

2.3 带参数的装饰器

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

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

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

say_hi()

输出:

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

三、类装饰器

装饰器不仅可以装饰函数,还可以装饰类。类装饰器接受一个类作为参数,并返回一个新的类。

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

@singleton
class DatabaseConnection:
    def __init__(self):
        print("创建数据库连接")
    
    def query(self, sql):
        print(f"执行查询:{sql}")

# 无论创建多少次,都只会有一个实例
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2)  # 输出:True

四、实际应用场景

4.1 日志记录装饰器

Python
import logging
from datetime import datetime

logging.basicConfig(level=logging.INFO)

def log_execution(func):
    """记录函数执行的日志装饰器"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        logging.info(f"开始执行 {func.__name__}, 参数:args={args}, kwargs={kwargs}")
        start_time = datetime.now()
        
        try:
            result = func(*args, **kwargs)
            logging.info(f"{func.__name__} 执行成功")
            return result
        except Exception as e:
            logging.error(f"{func.__name__} 执行失败:{str(e)}")
            raise
        finally:
            end_time = datetime.now()
            duration = (end_time - start_time).total_seconds()
            logging.info(f"{func.__name__} 执行耗时:{duration:.4f}秒")
    
    return wrapper

@log_execution
def calculate_sum(numbers):
    return sum(numbers)

calculate_sum([1, 2, 3, 4, 5])

4.2 缓存装饰器(记忆化)

Python
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]
    
    return wrapper

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

print(fibonacci(10))  # 会缓存中间结果
print(fibonacci(10))  # 直接从缓存获取

4.3 权限验证装饰器

Python
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} 角色,"
                    f"当前角色为 {user.get('role')}"
                )
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

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

# 测试
admin_user = {'name': 'Alice', 'role': 'admin'}
normal_user = {'name': 'Bob', 'role': 'user'}

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

4.4 重试装饰器

Python
import time
import random

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} 次尝试失败:{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_operation():
    """模拟不稳定的操作"""
    if random.random() < 0.7:
        raise ConnectionError("网络连接失败")
    return "操作成功"

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

4.5 性能分析装饰器

Python
import time
from collections import defaultdict

class PerformanceAnalyzer:
    """性能分析装饰器类"""
    stats = defaultdict(list)
    
    @classmethod
    def report(cls):
        print("\n=== 性能分析报告 ===")
        for func_name, times in cls.stats.items():
            avg_time = sum(times) / len(times)
            min_time = min(times)
            max_time = max(times)
            print(f"{func_name}:")
            print(f"  调用次数:{len(times)}")
            print(f"  平均耗时:{avg_time:.4f}秒")
            print(f"  最短耗时:{min_time:.4f}秒")
            print(f"  最长耗时:{max_time:.4f}秒")

def profile(func):
    """性能分析装饰器"""
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        end = time.perf_counter()
        duration = end - start
        PerformanceAnalyzer.stats[func.__name__].append(duration)
        return result
    return wrapper

@profile
def slow_function():
    time.sleep(0.1)

@profile
def fast_function():
    time.sleep(0.01)

for _ in range(5):
    slow_function()
    fast_function()

PerformanceAnalyzer.report()

五、装饰器链与执行顺序

多个装饰器可以叠加使用,执行顺序是从下往上(靠近函数定义的先执行):

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

test_function()

输出:

装饰器 A - 前
装饰器 B - 前
原函数执行
装饰器 B - 后
装饰器 A - 后

六、最佳实践与注意事项

6.1 使用 functools.wraps

始终使用 INLINE_CODE_6 来保留原函数的元信息,这对于调试和文档生成非常重要。

6.2 保持装饰器单一职责

每个装饰器应该只负责一个功能,这样便于组合和测试。

6.3 注意装饰器的性能开销

装饰器会增加函数调用的开销,在性能敏感的代码中需要谨慎使用。

6.4 避免过度嵌套

过深的嵌套会让代码难以理解和维护,必要时可以考虑使用类来实现装饰器。

6.5 文档化装饰器

为装饰器编写清晰的文档字符串,说明其用途、参数和返回值。

七、总结

装饰器是 Python 中最优雅和强大的特性之一。通过本文的学习,你应该已经掌握了:

  1. 装饰器的基本概念和工作原理
  2. 如何创建简单的函数装饰器
  3. 如何使用 functools.wraps 保留函数信息
  4. 如何创建带参数的装饰器
  5. 类装饰器的实现方法
  6. 多种实际应用场景的实现
  7. 装饰器链的执行顺序
  8. 装饰器的最佳实践

装饰器的学习曲线可能有些陡峭,但一旦掌握,它将极大地提升你的代码质量和开发效率。建议你在实际项目中多加练习,从简单的日志记录开始,逐步尝试更复杂的场景。

记住,好的装饰器应该是透明的、可组合的,并且遵循单一职责原则。当你发现自己在多个函数中重复相同的代码模式时,就是考虑使用装饰器的时候了。

希望这篇教程能够帮助你更好地理解和运用 Python 装饰器。祝你在编程的道路上越走越远!

分享到:

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

加载评论中...