懒人必备,五个高效Python装饰器

到目前为止,Python是我最喜欢的编程语言,因为它的语法简单,在机器学习和网络开发等各个领域的应用也很强大。

虽然我已经写了很多的代码,但除非绝对必要,我很少使用装饰器,比如使用@staticmethod装饰器来表示一个类中的静态方法。

然而,在最近的一次合并申请审查中,我的同事在我的一个函数中引入了一个定时器装饰器,这改变了我的看法。这激发了我对装饰器所能提供的众多其他功能的好奇心,提升了代码的清洁度和可读性。

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

Python包装器

Python 封装器是添加到另一个函数中的函数,然后可以添加额外的功能或修改其行为,而不直接改变其源代码。它们通常以装饰器的形式实现,这是一种特殊的函数,将另一个函数作为输入,并对其功能进行一些修改。

封装器函数在各种情况下都很有用:

  • 功能扩展(Functionality Extension):我们可以通过用装饰器包装我们的函数来增加诸如日志、性能测量或缓存等功能。

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

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

例子

让我告诉你一些例子,这些例子使包装器成为我们日常工作中的必备品:

1 — Timer

这个封装器函数测量一个函数的执行时间,并打印出已用的时间。它对于剖析和优化代码非常有用。

import time  
  
def timer(func):  
    def wrapper(*args, **kwargs):  
        # start the timer  
        start_time = time.time()  
        # call the decorated function  
        result = func(*args, **kwargs)  
        # remeasure the time  
        end_time = time.time()  
        # compute the elapsed time and print it  
        execution_time = end_time - start_time  
        print(f"Execution time: {execution_time} seconds")  
        # return the result of the decorated function execution  
        return result  
    # return reference to the wrapper function  
    return wrapper  

为了在Python中创建装饰器,我们需要定义一个叫做timer的函数,该函数接收一个叫做func的参数,表示它是一个装饰器函数。在定时器函数中,我们定义另一个叫做`wrapper’的函数,它接收通常传递给我们想要装饰的函数的参数。

在wrapper函数中,我们使用提供的参数调用所需的函数。我们可以这样做:result = func(*args, **kwargs)

最后,包装器函数返回被装饰的函数的执行结果。装饰器函数应该返回一个引用到我们刚刚创建的封装器函数。

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

@timer  
def train_model():  
    print("Starting the model training function...")  
    # simulate a function execution by pausing the program for 5 seconds    # 喜欢就关注@公众号:数据STUDIO  
    time.sleep(5)   
    print("Model training completed!")  
  
train_model() 
Starting the model training function…  
Model Training completed!  
Execution time: 5.006425619125366 seconds  

2 — Debugger

可以创建一个额外的有用的包装函数,通过打印每个函数的输入和输出来促进调试。这种方法使我们能够深入了解各种函数的执行流程,而不必用多个打印语句来干扰我们的应用程序。

def debug(func):  
    def wrapper(*args, **kwargs):  
        # print the fucntion name and arguments  
        print(f"Calling {func.__name__} with args: {args} kwargs: {kwargs}")  
        # call the function  
        result = func(*args, **kwargs)  
        # print the results  
        print(f"{func.__name__} returned: {result}")  
        return result  
    return wrapper  

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

@debug  
def add_numbers(x, y):  
    return x + y  
add_numbers(7, y=5,)    
# Output: Calling add_numbers with args: (7) kwargs: {'y': 5} \n add_numbers returned: 12  

3 — 异常处理程序

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

我们可以根据你的要求定制包装函数中的异常处理方式,例如记录异常或执行额外的错误处理步骤。

def exception_handler(func):  
    def wrapper(*args, **kwargs):  
        try:  
            return func(*args, **kwargs)  
        except Exception as e:  
            # Handle the exception  
            print(f"An exception occurred: {str(e)}")  
            # Optionally, perform additional error handling or logging  
            # Reraise the exception if needed  
    return wrapper  

这对于简化我们的代码和建立一个统一的处理异常和记录错误的程序变得非常有用。

@exception_handler  
def divide(x, y):  
    result = x / y  
    return result  
divide(10, 0)    
# Output: An exception occurred: division by zero  

4 — Input Validator

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

另一种方法是在我们想要验证输入数据的函数内创建无数的assert行,来实现这一目的。

为了给装饰添加验证,我们需要用另一个函数来包装装饰函数,该函数接收一个或多个验证函数作为参数。这些验证函数负责检查输入值是否符合某些标准或条件。

validate_input函数本身现在作为一个装饰器。在封装函数中,inputkeyword的参数会根据提供的验证函数进行检查。如果任何参数没有通过验证,就会引发一个 “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"Invalid argument: {val}")  
            for key, val in kwargs.items():  
                if key in validations[len(args):]:  
                    if not validations[len(args):][key](val):  
                        raise ValueError(f"Invalid argument: {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!")  # Output: Hello! 1.0  

5 — Retry

这个封装器会重试一个函数的执行,并在重试之间有一定的延迟。在处理网络或API调用时,它可能会因为临时问题而偶尔失败,因此很有用。

为了实现这一点,我们可以为我们的装饰器定义另一个包装函数,与我们之前的例子类似。然而,这次我们不是将验证函数作为输入变量,而是传递特定的参数,如max_attempsdelay

当装饰函数被调用时,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"Attempt {attempts} failed: {e}")  
                    time.sleep(delay)  
            print(f"Function failed after {max_attempts} attempts")  
        return wrapper  
    return decorator  

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

@retry(max_attempts=3, delay=2)  
def fetch_data(url):  
    print("Fetching the data..")  
    # raise timeout error to simulate a server not responding..  
    raise TimeoutError("Server is not responding.")  
fetch_data("https://example.com/data")    
# Retries 3 times with a 2-second delay between attempts  

总结

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

在这篇文章中,我们探讨了五个Python包装器的例子:

  • 计时器包装器

  • 调试器封装器

  • 异常处理程序包装器

  • 输入验证器包装器

  • 函数重试封装器

在你的项目中加入这些包装器将帮助你写出更干净、更有效的Python代码,并使你的编程技巧更上一层楼。

---------------------------END---------------------------

题外话

在这里插入图片描述

感兴趣的小伙伴,赠送全套Python学习资料,包含面试题、简历资料等具体看下方。

👉CSDN大礼包🎁:全网最全《Python学习资料》免费赠送🆓!(安全链接,放心点击)

一、Python所有方向的学习路线

Python所有方向的技术点做的整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照下面的知识点去找对应的学习资源,保证自己学得较为全面。

img
img

二、Python必备开发工具

工具都帮大家整理好了,安装就可直接上手!img

三、最新Python学习笔记

当我学到一定基础,有自己的理解能力的时候,会去阅读一些前辈整理的书籍或者手写的笔记资料,这些笔记详细记载了他们对一些技术点的理解,这些理解是比较独到,可以学到不一样的思路。

img

四、Python视频合集

观看全面零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。

img

五、实战案例

纸上得来终觉浅,要学会跟着视频一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。

img

六、面试宝典

在这里插入图片描述

在这里插入图片描述

简历模板在这里插入图片描述

👉CSDN大礼包🎁:全网最全《Python学习资料》免费赠送🆓!(安全链接,放心点击)

若有侵权,请联系删除

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值