再谈装饰器

作者: somenzz | 来源:发表于2019-04-25 08:44 被阅读15次

    昨天我分享了装饰器的使用方法,发现看的人并不多,这也正常,毕竟装饰器是一种锦上添花的东西,没有他,无法稍微麻烦点,但还是可以凑合着过的。

    其实,高手和普通人就差这一点,一般人觉得学得够当下所用了,也就不愿意再花时间学习了,这样也就不会再进步了,也就成不了高手。

    虽然我也不是高手,但我愿意持续学习,缩短与高手之间得距离。

    对于我们从事 IT 职业的,学习的东西一定要使用,如果工作上没有需求,那么就自己创造需求,自己来实现,只有这样,才能真正的学会。否则,当时懂了,时间一长,全忘了,花了时间,确毫无收获。

    我很喜欢布尔值,要么是 0 要么是 1。学习也是一样,要么不学,要么就学到 100%。

    下面,我们就来聊聊装饰器非常实用的应用场景。

    我们写程序时都会处理异常,有些异常是需要抛出的,有些异常是可以忽略的,还有些异常通过重跑几次就解决了。假如让你写个装饰器,当被装饰的函数调用抛出指定的异常时,函数会被重新调用,直到达到指定的最大调用次数才重新抛出指定的异常,你怎么写呢?

    假如有以下函数 func

    import time
    class ValueError(Exception):
        pass
    
    class CustomException(Exception):
        pass
    
    def func(num):
        time.sleep(1)
        print("func is called.")
        if num == 0:
            pass
        elif num == 1:
            raise CustomException
        elif num == 2:
            raise ValueError
        else:
            raise Exception
    

    那么:

    @retry(times=3,traced_exceptions=ValueError,reraised_exception=CustomException)
    def func(num):
    

    就表示当 func 抛出 ValueError 时自动重试 3 次,如果最后抛出的是 CustomException 就抛出异常,否则就什么也不抛出。我们还可以稍微增加点难度,比如:
    traced_exceptions 为监控的异常,可以为 None(默认)、异常类、或者一个异常类的列表。 traced_exceptions 如果为 None,则监控所有的异常;如果指定了异常类,则若函数调用抛出指定的异常时,重新调用函数,直至成功返回结果或者达到最大尝试次数,此时重新抛出原异常(reraised_exception 的值为 None)
    ,或者抛出由 reraised_exception 指定的异常。

    你可以自己先实现下,下面给出我自己的一种实现方法:

    def retry(times=10, traced_exceptions=None, reraised_exception=None):
        '''设计一个装饰器函数 retry,当被装饰的函数调用抛出指定的异常时,
        函数会被重新调用,直到达到指定的最大调用次数才重新抛出指定的异常。
        traced_exceptions 为监控的异常,可以为 None(默认)、异常类、或者一个异常类的列表。
        traced_exceptions 如果为 None,则监控所有的异常;如果指定了异常类,则若函数调用抛出指定的异常时,重新调用函数,直至成功返回结果
        或者达到最大尝试次数,此时重新抛出原异常(reraised_exception 的值为 None)
        ,或者抛出由 reraised_exception 指定的异常。
        '''
    
        def decorator(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                num = times
                need_raisse = False
                while True:
                    try:
                        return func(*args, **kwargs)
                    except Exception as e:
                        if traced_exceptions is None:#说明要捕捉所有异常,直接 pass
                            num -=1
                        elif isinstance(e,traced_exceptions):#如果指定了捕捉的异常类,则 pass
                            num -= 1
                        elif type(traced_exceptions) == list and type(e) in traced_exceptions:#如果指定了捕捉异常类的列表,则 pass
                            num -= 1
                        else: #需要抛出异常
                            need_raisse = True
    
                        if num == 0 or need_raisse:#重试次数完毕或非捕捉的异常类
                            if reraised_exception is None or type(e) == reraised_exception:
                                #reraised_exception 为 None 则抛出原来的异常,否则只抛出指定的异常
                                raise
                            else:
                                break
            return wrapper
        return decorator
    
    

    给出一种运行结果:

    @retry(times=3,traced_exceptions=ValueError,reraised_exception=ValueError) 
    

    对应的结果如下:

    func is called.
    func is called.
    func is called.
    Traceback (most recent call last):
      File "E:/test.py", line 65, in <module>
        func(2)
      File "E:/test.py", line 29, in wrapper
        return func(*args, **kwargs)
      File "E:/test.py", line 60, in func
        raise ValueError
    __main__.ValueError
    
    

    当你实现这个装饰器后,可以保存下来,后续的项目中肯定可以用得到,到时候就不用再造轮子了。

    如果你还不太理解装饰器的作用,请参考我的上篇文章我是装饰器。

    其他应用场景

    1、代码都写好了,现在要求插入日志。
    2、代码都写好了,现在要求加入计时功能、性能测试。
    3、代码都写好了,现在要求一个函数变成事务性操作。
    4、代码都写好了,现在又要求增加权限验证。

    有了装饰器,随你需求怎么变吧,反正我不改原有代码,就可以实现你的需求。

    (完)

    相关文章

      网友评论

        本文标题:再谈装饰器

        本文链接:https://www.haomeiwen.com/subject/znzngqtx.html