折腾侠
技术教程

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

折腾侠
2026/04/23 发布
1约 7 分钟1433 字 / 650 词00

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

引言

在 Python 编程世界中,装饰器(Decorator)是最强大也最容易被误解的特性之一。它允许你在不修改原函数代码的前提下,动态地增强或修改函数的行为。本文将带你从基础概念到高级应用,全面掌握 Python 装饰器的使用技巧。

什么是装饰器?

装饰器本质上是一个高阶函数,它接受一个函数作为参数,并返回一个新的函数。这种设计模式源于函数式编程中的"包装器"概念,让你能够在保持原函数功能的同时,添加额外的逻辑。

想象一下,你有一批礼物(函数),装饰器就是包装纸和丝带——它不改变礼物本身,但让礼物看起来更精美,功能更丰富。

基础语法与原理

最简单的装饰器

让我们从一个最基础的例子开始:

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

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

say_hello()

输出结果:

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

这里的 INLINE_CODE_0 语法糖等价于 INLINE_CODE_1。装饰器在函数定义时立即执行,将原函数替换为包装后的新函数。

保留函数元信息

上述基础版本存在一个问题:原函数的名称、文档字符串等元信息会丢失。使用 INLINE_CODE_2 可以解决这个问题:

Python
from functools import wraps

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

@preserve_metadata
def documented_function():
    """这是一个有文档的函数"""
    pass

print(documented_function.__name__)  # 输出:documented_function
print(documented_function.__doc__)   # 输出:这是一个有文档的函数

带参数的装饰器

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

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"Hello, {name}!")

greet("World")

输出:

Hello, World!
Hello, World!
Hello, World!

实用场景与代码示例

1. 性能计时器

性能优化中,我们经常需要测量函数执行时间:

Python
import time
from functools import wraps

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

@timer
def slow_operation():
    time.sleep(1)
    return "完成"

slow_operation()

2. 重试机制

网络请求或文件操作可能因临时故障失败,自动重试能提高系统稳定性:

Python
import random
from functools import wraps

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

@retry(max_attempts=3, delay=0.5)
def unstable_operation():
    if random.random() < 0.7:
        raise ConnectionError("网络连接失败")
    return "操作成功"

3. 权限验证

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

Python
from functools import wraps

def require_login(func):
    @wraps(func)
    def wrapper(user, *args, **kwargs):
        if not user.is_authenticated:
            raise PermissionError("用户未登录")
        return func(user, *args, **kwargs)
    return wrapper

def require_admin(func):
    @wraps(func)
    def wrapper(user, *args, **kwargs):
        if not user.is_admin:
            raise PermissionError("需要管理员权限")
        return func(user, *args, **kwargs)
    return wrapper

class User:
    def __init__(self, name, is_authenticated=True, is_admin=False):
        self.name = name
        self.is_authenticated = is_authenticated
        self.is_admin = is_admin

@require_login
@require_admin
def delete_user(admin_user, target_user):
    print(f"管理员 {admin_user.name} 删除了用户 {target_user}")
    return True

4. 缓存装饰器

对于计算密集型或 I/O 密集型操作,缓存结果能显著提升性能:

Python
from functools import wraps

def cache(func):
    cache_storage = {}
    
    @wraps(func)
    def wrapper(*args):
        if args in cache_storage:
            print(f"从缓存获取 {func.__name__}{args}")
            return cache_storage[args]
        
        print(f"计算 {func.__name__}{args}")
        result = func(*args)
        cache_storage[args] = result
        return result
    
    return wrapper

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

print(fibonacci(10))  # 第一次计算
print(fibonacci(10))  # 从缓存获取

5. 日志记录

在生产环境中,记录函数调用信息对调试和监控至关重要:

Python
import logging
from datetime import datetime
from functools import wraps

logging.basicConfig(level=logging.INFO)

def log_calls(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        timestamp = datetime.now().isoformat()
        logging.info(f"[{timestamp}] 调用 {func.__name__}, 参数:args={args}, kwargs={kwargs}")
        
        try:
            result = func(*args, **kwargs)
            logging.info(f"[{timestamp}] {func.__name__} 返回:{result}")
            return result
        except Exception as e:
            logging.error(f"[{timestamp}] {func.__name__} 异常:{e}")
            raise
    
    return wrapper

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

divide(10, 2)

类装饰器

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

Python
class CountCalls:
    def __init__(self, func):
        wraps(func)(self)
        self.func = func
        self.call_count = 0
    
    def __call__(self, *args, **kwargs):
        self.call_count += 1
        print(f"第 {self.call_count} 次调用 {self.func.__name__}")
        return self.func(*args, **kwargs)

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

greet("Alice")
greet("Bob")
print(f"总调用次数:{greet.call_count}")

装饰器栈与执行顺序

多个装饰器可以叠加使用,执行顺序是从下到上(从内到外):

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 - 后

实际项目中的应用建议

1. 选择合适的抽象层级

装饰器应该专注于单一职责。不要在一个装饰器中混合日志、缓存、权限验证等多个功能。每个装饰器只做一件事,并做好。

2. 注意性能开销

装饰器会引入额外的函数调用开销。在性能敏感的代码路径中,谨慎使用多层装饰器。对于高频调用的函数,考虑内联装饰器逻辑。

3. 保持可测试性

装饰器应该易于单元测试。避免在装饰器中依赖全局状态,使用依赖注入让测试更简单。

4. 文档化装饰器行为

使用装饰器的函数行为可能不明显。在文档中说明装饰器的效果,帮助其他开发者理解代码。

常见陷阱与解决方案

陷阱 1:装饰器不保留函数签名

问题:IDE 无法正确提示参数,工具无法分析函数签名。

解决:始终使用 INLINE_CODE_4,或在 Python 3.3+ 中使用 INLINE_CODE_5 保留签名信息。

陷阱 2:装饰器在模块加载时执行

问题:装饰器在导入模块时就执行,可能导致意外副作用。

解决:确保装饰器逻辑是惰性的,只在被装饰函数被调用时才执行。

陷阱 3:方法装饰器与 self 参数

问题:类方法装饰器需要正确处理 INLINE_CODE_6 参数。

解决:使用 INLINE_CODE_7 通配参数,或专门编写类方法装饰器。

结语

Python 装饰器是提升代码质量、减少重复、增强可维护性的强大工具。从简单的日志记录到复杂的权限控制,装饰器模式在现代 Python 开发中无处不在。

掌握装饰器的关键在于理解其本质:函数包装器。一旦理解了这个核心概念,你就能灵活运用装饰器解决各种实际问题。

建议的学习路径:

  1. 从基础语法开始,手动实现几个简单装饰器
  2. 理解 INLINE_CODE_8 的重要性并养成使用习惯
  3. 掌握带参数装饰器的三层嵌套模式
  4. 在实际项目中应用,从日志、计时等简单场景开始
  5. 逐步探索类装饰器、装饰器栈等高级用法

记住,好的装饰器应该让代码更清晰,而不是更复杂。当你发现装饰器让代码难以理解时,也许是时候重新设计了。


关于作者:本文是 Python 进阶系列教程的一部分,旨在帮助开发者深入理解 Python 的核心特性。欢迎在实践中探索更多装饰器的创意用法。

分享到:

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

加载评论中...