首页
开发技巧正文内容

五个可以将您的代码减少一半的Python装饰器

2023年11月06日
阅读时长 2 分钟
阅读量 2
五个可以将您的代码减少一半的Python装饰器

通过使用这些包装器来提高效率和可读性,提升您的Python编程水平。

Photo by freestocks on Unsplash

Python是我最喜欢的编程语言,因为它的语法简单,可以在机器学习和Web开发等各个领域中应用。

虽然我已经编程五年了,但装饰器在我的视野中很少出现,除非绝对必要,比如使用@staticmethod装饰器来表示类中的静态方法。

然而,在最近的合并请求审查中,当我的同事向我的一个函数引入了一个计时器装饰器时,我的看法发生了改变。这激发了我对装饰器可以提供的其他功能的好奇心,提高了代码的整洁性和可读性。

因此,在这篇简短的文章中,我们将探讨Python包装器的概念,并介绍五个可以改进我们的Python开发过程的示例。

Python包装器

Python包装器是添加到另一个函数中的函数,它可以在不直接更改源代码的情况下添加附加功能或修改其行为。它们通常被实现为装饰器,装饰器是一种特殊的函数,它以另一个函数作为输入,并对其功能进行一些更改。

包装器函数在各种场景中非常有用:

  • 功能扩展:我们可以通过使用装饰器将日志记录、性能测量或缓存等功能添加到我们的函数中。

  • 代码重用:我们可以将包装器函数或甚至类应用于多个实体,以避免代码重复,并确保在不同组件之间保持一致的行为。

  • 行为修改:我们可以拦截输入参数,例如验证输入变量,而无需使用许多assert语句。

示例

让我给您展示一些在我们日常工作中必不可少的示例:

1 - 计时器

这个包装器函数测量函数的执行时间并打印出经过的时间。它对于性能分析和代码优化非常有用。

import time

def timer(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"执行时间:{execution_time}秒")
        # 返回被装饰的函数的执行结果
        return result
    # 返回对包装函数的引用
    return wrapper

要在Python中创建装饰器,我们需要定义一个名为timer的函数,该函数接受一个名为func的参数,以指示它是一个装饰器函数。在timer函数内部,我们定义另一个名为wrapper的函数,该函数接受通常传递给我们要装饰的函数的参数。

wrapper函数内部,我们使用提供的参数调用所需的函数。我们可以使用以下代码行来实现这一点:result = func(*args, **kwargs)

最后,wrapper函数返回被装饰函数的执行结果。装饰器函数应该返回对我们刚刚创建的wrapper函数的引用

要使用装饰器,您可以使用@符号将其应用于所需的函数。

@timer
def train_model():
    print("开始模型训练函数...")
    # 通过暂停程序5秒钟来模拟函数执行
    time.sleep(5) 
    print("模型训练完成!")

train_model() 

输出:

开始模型训练函数...

模型训练完成!

执行时间:5.006425619125366秒


2 - 调试器

另一个有用的包装器函数可以用于简化调试,通过打印每个函数的输入和输出来帮助我们了解各个函数的执行流程,而不会在应用程序中添加多个打印语句。

def debug(func):
    def wrapper(*args, **kwargs):
        # 打印函数名和参数
        print(f"调用 {func.__name__},参数:{args},关键字参数:{kwargs}")
        # 调用函数
        result = func(*args, **kwargs)
        # 打印结果
        print(f"{func.__name__} 返回:{result}")
        return result
    return wrapper

我们可以使用__name__参数来获取被调用的函数的名称,然后使用argskwargs参数来打印传递给函数的参数。

@debug
def add_numbers(x, y):
    return x + y
add_numbers(7, y=5,)  # 输出:调用 add_numbers,参数:(7),关键字参数:{'y': 5} \n add_numbers 返回:12

3 - 异常处理器

exception_handler装饰器将捕获divide函数中引发的任何异常并进行相应的处理。

我们可以根据需要自定义包装器函数中的异常处理,例如记录异常或执行其他错误处理步骤。

def exception_handler(func):
    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Exception as e:
            # 处理异常
            print(f"发生异常:{str(e)}")
            # 可选地,执行其他错误处理或记录
            # 如果需要,重新引发异常
    return wrapper

这对于简化我们的代码并建立统一的异常处理和错误日志记录流程非常有用。

@exception_handler
def divide(x, y):
    result = x / y
    return result
divide(10, 0)  # 输出:发生异常:division by zero

4 - 输入验证器

这个包装器函数根据指定的条件或数据类型验证函数的输入参数。它可以用于确保输入数据的正确性和一致性。

另一种方法是在我们要验证输入数据的函数内部创建无数个assert语句。

要向装饰器添加验证,我们需要使用另一个函数将装饰器函数包装起来,该函数接受一个或多个验证函数作为参数。这些验证函数负责检查输入值是否满足某些条件或标准。

validate_input函数本身现在充当装饰器。在wrapper函数内部,将检查输入关键字参数是否符合提供的验证函数。如果任何参数未通过验证,它将引发一个带有指示无效参数的消息的ValueError

def validate_input(*validations):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for i, val in enumerate(args):
                if i < len(validations):
                    if not validations[i](val):
                        raise ValueError(f"无效参数:{val}")
            for key, val in kwargs.items():
                if key in validations[len(args):]:
                    if not validations[len(args):][key](val):
                        raise ValueError(f"无效参数:{key}={val}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

要调用经过验证的输入,我们需要定义验证函数。例如,可以使用两个验证函数。第一个函数(lambda x: x > 0)检查参数x是否大于0,第二个函数(lambda y: isinstance(y, str))检查参数y是否为字符串类型。

确保验证函数的顺序与它们要验证的参数的顺序相对应非常重要。

@validate_input(lambda x: x > 0, lambda y: isinstance(y, str))
def divide_and_print(x, message):
    print(message)
    return 1 / x

divide_and_print(5, "Hello!")  # 输出:Hello! 1.0

5 - 重试

这个包装器函数会在指定的次数内重试执行函数,并在重试之间延迟一段时间。当处理由于临时问题而偶尔失败的网络或API调用时,它非常有用。

要实现这一点,我们可以向装饰器添加另一个包装器函数,类似于我们之前的示例。但是这次,我们不再提供验证函数作为输入变量,而是可以传递特定的参数,如max_attemptsdelay

当调用被装饰的函数时,将调用wrapper函数。它会跟踪尝试的次数(从0开始)并进入一个while循环。循环尝试执行被装饰的函数,并在成功时立即返回结果。但是,如果发生异常,它会增加尝试计数器并打印一个错误消息,指示尝试编号和发生的具体异常。然后,它使用time.sleep等待指定的延迟时间,然后再次尝试执行函数。

import time

def retry(max_attempts, delay=1):
    def decorator(func):
        def wrapper(*args, **kwargs):
            attempts = 0
            while attempts < max_attempts:
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    attempts += 1
                    print(f"第{attempts}次尝试失败:{e}")
                    time.sleep(delay)
            print(f"在{max_attempts}次尝试后函数失败")
        return wrapper
    return decorator

为了调用该函数,我们可以指定最大尝试次数和每次调用函数之间的时间间隔(以秒为单位)。

@retry(max_attempts=3, delay=2)
def fetch_data(url):
    print("正在获取数据..")
    # 模拟服务器无响应的超时错误..
    raise TimeoutError("服务器无响应。")
fetch_data("https://example.com/data")  # 重试3次,每次间隔2秒

结论

Python包装器是强大的工具,可以提升您的Python编程体验。通过使用包装器,您可以简化复杂的任务,提高代码可读性,并提高生产力。

在本文中,我们探讨了五个Python包装器的示例:

  • 计时器包装器

  • 调试器包装器

  • 异常处理器包装器

  • 输入验证器包装器

  • 函数重试包装器

将这些包装器应用到您的项目中,将有助于编写更清晰、更高效的Python代码,并提升您的编程技能。

免责声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

相关文章

探索多种软件架构模式及其实用应用
2024年11月22日19:06
本文深入探讨了多种软件架构模式,包括有界上下文、边车模式、发布-订阅模式、应用网关、微服务、命令职责分离(CQRS)等,介绍了它们的优点、使用场景以及具体应用实例。文章强调根据具体项目需求和团队能力选择最合适的架构,以构建高效和可维护的解决方案,同时展示了各架构模式间的综合应用,提供了丰富的案例和技术细节。
15个高级Python快捷键助您更快编程
2024年11月21日07:02
本文分享了 15 个高级的 Python 编程快捷键,包括上下文管理器、行内字典合并、函数参数解包、链式比较、dataclasses、海象运算符、反转列表、备忘录缓存、splitlines、enumerate、字典推导、zip 用于并行迭代、itertools.chain 扁平化列表、functools.partial 部分函数和 os.path 文件路径管理等,帮助开发者提高编程效率和代码简洁性。
揭示网页开发的 11 个迷思:停止相信这些误区
2024年11月19日22:05
网页开发充满误解,这篇博文针对11个常见迷思进行揭秘。包括网站开发后不需更新、需掌握所有技术、AI会取代开发者等。强调持续学习、专业化、用户体验的重要性,澄清误区如多任务处理的必要性和最新技术的必需性。文章提醒开发者注重实用而非追求完美代码,以务实态度面对开发工作。
你知道 CSS 的四种 Focus 样式吗?
2024年11月18日21:41
本文介绍了四种 CSS focus 样式::focus、:focus-visible、:focus-within 以及自定义的 :focus-visible-within,帮助提升网站用户体验。:focus 样式应用于被选中元素;:focus-visible 仅在键盘导航时显示;:focus-within 用于父元素;自定义 :focus-visible-within 结合两者效果。合理运用这些样式能使网站更方便键盘用户导航。
利用 Python 实现自动化图像裁剪:简单高效的工作流程
2024年11月11日20:49
使用 Python 和 OpenCV 自动裁剪图像,轻松实现 16:9 的完美构图。这个指南介绍了如何通过代码进行灰度化、模糊处理和边缘检测,最终识别出最重要的部分进行裁剪。特别适合需要批量处理图像的情况,节省大量时间。
每位资深前端开发人员都应了解的 TypeScript 高级概念
2024年11月11日02:07
资深前端开发者应了解 TypeScript 的高级概念,如联合类型、交叉类型、类型保护、条件类型、映射类型、模板字面量类型和递归类型。这些特性可提升代码的可维护性和可扩展性,确保在开发复杂应用时实现更高的类型安全性和效率。