查看原文
其他

再谈装饰器

somenzz Python七号 2022-04-12

阅读本文大概需要 2~3 分钟。

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

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

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

学以致用,对于我们从事 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(默认)、异常类、或者一个异常类的列表,如果为 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 65in <module>
    func(2)
  File "E:/test.py", line 29in wrapper
    return func(*args, **kwargs)
  File "E:/test.py", line 60in func
    raise ValueError
__main__.ValueError

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

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

其他应用场景

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

有了装饰器,随你需求怎么变吧,反正我不改原有代码,就可以实现你的需求。欢迎阅读原文留言交流。

(完)

专注于有价值的分享

欢迎订阅、在看、转发


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存