当前位置:首页 > Python > 正文内容

Python 装饰器实战:从原理到高级应用

admin2周前 (03-22)Python24

在 Python 开发中,我们经常需要在函数执行前后添加额外的逻辑,比如日志记录、性能计时、权限验证等。如果直接修改函数内部代码,会导致代码重复和耦合。装饰器正是为了解决这个问题而生。

什么是装饰器?

装饰器本质上是一个高阶函数,它接受一个函数作为参数,并返回一个新的函数。语法糖 @ 让装饰器的使用变得优雅简洁。让我们从最简单的例子开始:

基础装饰器示例

假设我们想要在函数执行时打印一条日志:

def log_execution(func):
    def wrapper(*args, **kwargs):
        print(f"正在执行函数: {func.__name__}")
        result = func(*args, **kwargs)
        print(f"函数 {func.__name__} 执行完成")
        return result
    return wrapper

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

# 调用函数
resultresult = calculate_sum([1, 2, 3, 4, 5])
print(f"计算结果: {result}")

装饰器工作原理

当我们使用 @log_execution 时,Python 实际上执行了:calculate_sum = log_execution(calculate_sum)。原本的 calculate_sum 函数被替换为了 wrapper 函数,wrapper 函数会在调用原始函数前后执行额外逻辑。

带参数的装饰器

有时候我们需要让装饰器接受参数,这就需要再包一层函数:

def repeat_execution(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            results = []
            for i in range(times):
                print(f"第 {i 1} 次执行")
                result = func(*args, **kwargs)
                results.append(result)
            return results
        return wrapper
    return decorator

@repeat_execution(times=3)
def process_data(data):
    return [x * 2 for x in data]

# 调用将执行 3 次
resultsresults = process_data([1, 2, 3])

实战场景 1:性能计时装饰器

在性能优化时,我们需要矩道函数的执行时间。用装饰器可以轻松实现:

import time
from functools import wraps

def timing(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time Time.time()
        result = func(*args, **kwargs)
        end_time Time.time()
        execution_time = end_time - start_time
        print(f"函数 {func.__name__} 执行时间: {execution_time:.4f} 秒")
        return result
    return wrapper

@timing
def slow_operation():
    time.sleep(1.5)
    return "操作完成"

slow_operation()

为什么要使用 functools.wraps?

装饰器会替换原始函数,导致函数的元信息(如 __name__、__doc__)丢失。使用 @wraps 可以保留这些信息:

from functools import wraps

def better_decorator(func):
    @wraps(func) # 保留原始函数的元信息
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

@better_decorator
def my_function():
    """这是一个重要的函数"""
    pass

print(my_function.__name__) # 输出: my_function
print(my_function.__doc__)  # 输出: 这是一个重要的函数

实战场景 2:缓存装饰器

对于耗时且参数相同的计算,我们可以用缓存避免重复计算:

from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n-1)   fibonacci(n-2)

# 第一次调用会计算
print(fibonacci(50))
# 第二次调用直接从缓存读取
print(fibonacci(50))

自定义缓存装饰器

我们也可以自己实现一个简单的缓存装饰器:

def simple_cache(func):
    cache = {}
    def wrapper(*args):
        if args in cache:
            print(f"从缓存读取: {args}")
            return cache[args]
        result = func(*args)
        cache[args] = result
        return result
    return wrapper

@simple_cache
def expensive_calculation(x):
    print(f"正在计算: {x}")
    return x ** 2

expensive_calculation(10) # 计算并缓存
expensive_calculation(10) # 从缓存读取

实战场景 3:权限验证装饰器

在 Web 开发中,装饰器常用于权限验证:

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

@require_admin
def delete_user(admin_user, user_id):
    print(f"删除用户: {user_id}")
    return "删除成功"

admin = {name: admin, is_admin: True}
delete_user(admin, 123)

实战场景 4:重试装饰器

对于可能失败的网络请求,我们可以用装饰器实现自动重试:

import random

def retry(max_attempts=3, delay=1):
    def decorator(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} 次尝试失败,准备重试...")
                    time.sleep(delay)
        return wrapper
    return decorator

@retry(max_attempts=3)
def unstable_api_call():
    # 模拟有 70% 概率失败
    if random.random() < 0.7:
        raise ConnectionError("连接失败")
    return "请求成功"

unstable_api_call()

类装饰器

除了函数装饰器,Python 还支持类装饰器。类装饰器是一个实现了 __call__ 方法的类:

class CountCalls:
    def __init__(self, func):
        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():
    print("Hello!")

say_hello()
say_hello()

装饰器最佳实践

1. 始终使用 functools.wraps 保留函数元信息

2. 装饰器应该专注于单一职责,不要做太多事情

3. 在文档字符串中说明装饰器的行为

4. 考虑装饰器的执行顺序,多个装饰器会从下到上udi 执行

多个装饰器的组合

@timing
@log_execution
def complex_task():
    time.sleep(0.5)
    return "任务完成"

# 执行顺序:log_execution -> timing -> complex_task
complex_task()

总结

装饰器是 Python 中优雅而强大的工具,它可以帮助我们:

- 分离关注点,避免代码重复

- 提高代码的可读性和可维护性

- 在不修改%E原始函数的情况下增强功能

掌握装饰器将让你的 Python 代码更加简洁、优雅和专业。

相关文章

[Python 教程] Pandas 数据分析实战

Pandas 数据分析实战 Pandas 是 Python 数据分析的核心库,提供 DataFrame 和 Series 数据结构。本文介绍 Pandas 的实用技巧。 一、创建 DataFrame...

[Python 教程] Python 多线程编程指南

Python 多线程编程指南 Python 的 threading 模块提供多线程支持。本文介绍多线程编程的基础和实用技巧。 一、创建线程 import threading import time...

Python 装饰器实用技巧:从入门到精通

装饰器是 Python 最强大的特性之一,但也是很多开发者感到困惑的概念。简单来说,装饰器是一个函数,它接受另一个函数作为输入,并返回一个新的函数。使用装饰器,你可以在不修改原函数代码的情况下,为其添...

Python 上下文管理器:从入门到实战

在 Python 编程中,资源管理是一个永恒的话题。无论是打开文件、连接数据库,还是获取网络资源,我们都需要确保在使用完毕后正确释放这些资源。传统的 try-finally 模式虽然有效,但代码冗长且...

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

装饰器(Decorator)是 Python中一种优雅的设计模式,它允许我们在不修改原函数代码的前提下,动态地添加功能。想象一下,你有一个已经写好的函数,现在需要为它添加日志记录、性能监控、权限验证等...

Python 上下文管理器实战:从 with 语句到自定义资源管理

在 Python 编程中,上下文管理器(Context Manager)是一个强大但常被低估的特性。当你使用 open() 函数读取文件时,那个熟悉的 with 语句背后,正是上下文管理器在默默工作。...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法和观点。