【问题标题】:How to retry after exception?异常后如何重试?
【发布时间】:2011-01-06 05:45:42
【问题描述】:

我有一个以for i in range(0, 100) 开头的循环。通常它可以正常运行,但有时会由于网络状况而失败。目前我已将其设置为在失败时,它将在 except 子句中 continue(继续到 i 的下一个数字)。

我是否可以将相同的编号重新分配给 i 并再次运行循环的失败迭代?

【问题讨论】:

标签: python loops exception try-except


【解决方案1】:

2021 年 12 月 1 日更新

自 2016 年 6 月起,不再维护重试包。 考虑使用活动分支github.com/jd/tenacity,或者github.com/litl/backoff


retrying package 是在失败时重试代码块的好方法。

例如:

@retry(wait_random_min=1000, wait_random_max=2000)
def wait_random_1_to_2_s():
    print("Randomly wait 1 to 2 seconds between retries")

【讨论】:

【解决方案2】:

我用这个,可以用在任何功能上:

def run_with_retry(func: callable, max_retries: int = 3, wait_seconds: int = 2, **func_params):
num_retries = 1
while True:
    try:
        return func(*func_params.values())
    except Exception as e:
        if num_retries > max_retries:
            print('we have reached maximum errors and raising the exception')
            raise e
        else:
            print(f'{num_retries}/{max_retries}')
            print("Retrying error:", e)
            num_retries += 1
            sleep(wait_seconds)

这样调用:

    def add(val1, val2):
        return val1 + val2

    run_with_retry(func=add, param1=10, param2=20)

【讨论】:

    【解决方案3】:

    有了这个装饰器,你可以轻松控制错误

    class catch:
        def __init__(self, max=1, callback=None):
            self.max = max 
            self.callback = callback 
        
        def set_max(self, max):
            self.max = max
        
        def handler(self, *args, **kwargs):
            self.index = 0
            while self.index < self.max: 
                self.index += 1
                try:
                    self.func(self, *args, **kwargs)
            
                except Exception as error:
                    if callable(self.callback):
                        self.callback(self, error, args, kwargs)
                    
        def __call__(self, func):
            self.func = func
            return self.handler
    
    import time
    def callback(cls, error, args, kwargs):
        print('func args', args, 'func kwargs', kwargs)
        print('error', repr(error), 'trying', cls.index)
        if cls.index == 2:
            cls.set_max(4)
        
        else:
            time.sleep(1)
        
        
    @catch(max=2, callback=callback)  
    def test(cls, ok, **kwargs):
        raise ValueError('ok')
    
    test(1, message='hello')
    

    【讨论】:

      【解决方案4】:

      我喜欢为此使用布尔值,如下所示:

      success = False
      num_try = 0
      while success is False:
          if num_try >= 10: # or any number
              # handle error how  you please
          try:
              # code
              success = True
          except Exception as e:
              # record or do something with exception if needed
              num_try += 1
      

      【讨论】:

        【解决方案5】:

        装饰器是个好办法。

        from functools import wraps
        import time
        
        class retry:
            def __init__(self, success=lambda r:True, times=3, delay=1, raiseexception=True, echo=True):
                self.success = success
                self.times = times
                self.raiseexception = raiseexception
                self.echo = echo
                self.delay = delay
            def retry(fun, *args, success=lambda r:True, times=3, delay=1, raiseexception=True, echo=True, **kwargs):
                ex = Exception(f"{fun} failed.")
                r = None
                for i in range(times):
                    if i > 0:
                        time.sleep(delay*2**(i-1))
                    try:
                        r = fun(*args, **kwargs)
                        s = success(r)
                    except Exception as e:
                        s = False
                        ex = e
                        # raise e
                    if not s:
                        continue
                    return r
                else:
                    if echo:
                        print(f"{fun} failed.", "args:", args, kwargs, "\nresult: %s"%r)
                    if raiseexception:
                        raise ex
            def __call__(self, fun):
                @wraps(fun)
                def wraper(*args, retry=0, **kwargs):
                    retry = retry if retry>0 else self.times
                    return self.__class__.retry(fun, *args, 
                                                success=self.success, 
                                                times=retry,
                                                delay=self.delay,
                                                raiseexception = self.raiseexception,
                                                echo = self.echo,
                                                **kwargs)
                return wraper
        

        一些使用示例:

        @retry(success=lambda x:x>3, times=4, delay=0.1)
        def rf1(x=[]):
            x.append(1)
            print(x)
            return len(x)
        > rf1()
        
        [1]
        [1, 1]
        [1, 1, 1]
        [1, 1, 1, 1]
        
        4
        
        @retry(success=lambda x:x>3, times=4, delay=0.1)
        def rf2(l=[], v=1):
            l.append(v)
            print(l)
            assert len(l)>4
            return len(l)
        > rf2(v=2, retry=10) #overwite times=4
        
        [2]
        [2, 2]
        [2, 2, 2]
        [2, 2, 2, 2]
        [2, 2, 2, 2, 2]
        
        5
        
        > retry.retry(lambda a,b:a+b, 1, 2, times=2)
        
        3
        
        > retry.retry(lambda a,b:a+b, 1, "2", times=2)
        
        TypeError: unsupported operand type(s) for +: 'int' and 'str'
        

        【讨论】:

          【解决方案6】:

          retrying 的替代方案:tenacitybackoff(2020 年更新)

          retrying 库以前是要走的路,但遗憾的是它有一些错误,自 2016 年以来没有任何更新。其他替代品似乎是 backofftenacity。在写这篇文章的时候,坚韧有更多的 GItHub 星(2.3k vs 1.2k)并且最近更新,因此我选择使用它。这是一个例子:

          from functools import partial
          import random # producing random errors for this example
          
          from tenacity import retry, stop_after_delay, wait_fixed, retry_if_exception_type
          
          # Custom error type for this example
          class CommunicationError(Exception):
              pass
          
          # Define shorthand decorator for the used settings.
          retry_on_communication_error = partial(
              retry,
              stop=stop_after_delay(10),  # max. 10 seconds wait.
              wait=wait_fixed(0.4),  # wait 400ms 
              retry=retry_if_exception_type(CommunicationError),
          )()
          
          
          @retry_on_communication_error
          def do_something_unreliable(i):
              if random.randint(1, 5) == 3:
                  print('Run#', i, 'Error occured. Retrying.')
                  raise CommunicationError()
          
          for i in range(100):
              do_something_unreliable(i)
          

          上面的代码输出如下:

          Run# 3 Error occured. Retrying.
          Run# 5 Error occured. Retrying.
          Run# 6 Error occured. Retrying.
          Run# 6 Error occured. Retrying.
          Run# 10 Error occured. Retrying.
          .
          .
          .
          

          tenacity.retry 的更多设置列于tenacity GitHub page

          【讨论】:

          • 很好的答案!我花了几个小时试图找出装饰器,部分东西......你能让你的 retry_on_communication_error 成为一个可以接受参数的函数吗?类似于 def retry_on_communication_error(para1, para2)?
          • 我不完全确定你的意思,但是(1)如果你想将更多的参数传递给要重试的函数,只需在函数定义中添加更多的参数。在这个例子中,有do_something_unreliable(i),但如果你愿意,你也可以有do_something_unreliable(i, j, k)。 (2)如果你想为tenacity.retry传递一些其他参数,只需将它们添加到partial即可。
          【解决方案7】:

          这是我对这个问题的看法。以下retry 函数支持以下功能:

          • 成功时返回被调用函数的值
          • 如果尝试用尽,则引发调用函数的异常
          • 尝试次数限制(0 表示无限制)
          • 尝试之间的等待(线性或指数)
          • 仅当异常是特定异常类型的实例时重试。
          • 可选的尝试记录
          import time
          
          def retry(func, ex_type=Exception, limit=0, wait_ms=100, wait_increase_ratio=2, logger=None):
              attempt = 1
              while True:
                  try:
                      return func()
                  except Exception as ex:
                      if not isinstance(ex, ex_type):
                          raise ex
                      if 0 < limit <= attempt:
                          if logger:
                              logger.warning("no more attempts")
                          raise ex
          
                      if logger:
                          logger.error("failed execution attempt #%d", attempt, exc_info=ex)
          
                      attempt += 1
                      if logger:
                          logger.info("waiting %d ms before attempt #%d", wait_ms, attempt)
                      time.sleep(wait_ms / 1000)
                      wait_ms *= wait_increase_ratio
          

          用法:

          def fail_randomly():
              y = random.randint(0, 10)
              if y < 10:
                  y = 0
              return x / y
          
          
          logger = logging.getLogger()
          logger.setLevel(logging.INFO)
          logger.addHandler(logging.StreamHandler(stream=sys.stdout))
          
          logger.info("starting")
          result = retry.retry(fail_randomly, ex_type=ZeroDivisionError, limit=20, logger=logger)
          logger.info("result is: %s", result)
          

          请查看我的post 了解更多信息。

          【讨论】:

            【解决方案8】:

            attempts = 3
            while attempts:
              try:
                 ...
                 ...
                 <status ok>
                 break
              except:
                attempts -=1
            else: # executed only break was not  raised
               <status failed>

            【讨论】:

              【解决方案9】:

              我最近使用我的 python 解决了这个问题,我很高兴与 stackoverflow 访问者分享它,如果需要,请提供反馈。

              print("\nmonthly salary per day and year converter".title())
              print('==' * 25)
              
              
              def income_counter(day, salary, month):
                  global result2, result, is_ready, result3
                  result = salary / month
                  result2 = result * day
                  result3 = salary * 12
                  is_ready = True
                  return result, result2, result3, is_ready
              
              
              i = 0
              for i in range(5):
                  try:
                      month = int(input("\ntotal days of the current month: "))
                      salary = int(input("total salary per month: "))
                      day = int(input("Total Days to calculate> "))
                      income_counter(day=day, salary=salary, month=month)
                      if is_ready:
                          print(f'Your Salary per one day is: {round(result)}')
                          print(f'your income in {day} days will be: {round(result2)}')
                          print(f'your total income in one year will be: {round(result3)}')
                          break
                      else:
                          continue
                  except ZeroDivisionError:
                      is_ready = False
                      i += 1
                      print("a month does'nt have 0 days, please try again")
                      print(f'total chances left: {5 - i}')
                  except ValueError:
                      is_ready = False
                      i += 1
                      print("Invalid value, please type a number")
                      print(f'total chances left: {5 - i}')
              

              【讨论】:

                【解决方案10】:
                for _ in range(5):
                    try:
                        # replace this with something that may fail
                        raise ValueError("foo")
                
                    # replace Exception with a more specific exception
                    except Exception as e:
                        err = e
                        continue
                
                    # no exception, continue remainder of code
                    else:
                        break
                
                # did not break the for loop, therefore all attempts
                # raised an exception
                else:
                    raise err
                

                我的版本与上面的几个类似,但不使用单独的while 循环,如果所有重试都失败,则重新引发最新的异常。可以在顶部显式设置err = None,但不是绝对必要的,因为它应该只在出现错误时执行最终的else 块,因此设置了err

                【讨论】:

                • 我认为这是最好看的模式。但是,对于不是来自 python 的人来说,“err”超出了 for 块的范围真的很奇怪。
                • 是的,在某些方面在顶部使用明确的err = None 可能看起来更好,但正如我所指出的,我认为它实际上从未使用过。事实上,可以省略 else 并使用 golang-esque if errif err is not None 以防 err 可能以某种方式具有错误的值。
                【解决方案11】:

                我在我的代码中使用以下内容,

                   for i in range(0, 10):
                    try:
                        #things I need to do
                    except ValueError:
                        print("Try #{} failed with ValueError: Sleeping for 2 secs before next try:".format(i))
                        time.sleep(2)
                        continue
                    break
                

                【讨论】:

                  【解决方案12】:

                  如果您想要一个没有嵌套循环并在成功时调用break 的解决方案,您可以为任何可迭代开发一个快速包装retriable。这是我经常遇到的网络问题的示例 - 保存的身份验证过期。它的用法是这样的:

                  client = get_client()
                  smart_loop = retriable(list_of_values):
                  
                  for value in smart_loop:
                      try:
                          client.do_something_with(value)
                      except ClientAuthExpired:
                          client = get_client()
                          smart_loop.retry()
                          continue
                      except NetworkTimeout:
                          smart_loop.retry()
                          continue
                  

                  【讨论】:

                    【解决方案13】:

                    带有超时的通用解决方案:

                    import time
                    
                    def onerror_retry(exception, callback, timeout=2, timedelta=.1):
                        end_time = time.time() + timeout
                        while True:
                            try:
                                yield callback()
                                break
                            except exception:
                                if time.time() > end_time:
                                    raise
                                elif timedelta > 0:
                                    time.sleep(timedelta)
                    

                    用法:

                    for retry in onerror_retry(SomeSpecificException, do_stuff):
                        retry()
                    

                    【讨论】:

                    • 是否可以指定一个单独的函数进行错误检查?它将获取回调的输出并传递给错误检查函数来决定它是失败还是成功,而不是使用简单的except exception:
                    • 您可以使用if 语句来代替try … except。但它不像pythonic。
                    • 此解决方案不起作用。 trinket.io/python/caeead4f6b do_stuff 抛出的异常不会冒泡到生成器。为什么会呢? do_stuff 在 for 循环体中被调用,它本身在外层,而不是嵌套在生成器中。
                    • 您的权利,但出于不同的原因:永远不会调用 callback 函数。括号忘记了,换成callback()
                    【解决方案14】:

                    您可以使用 Python 重试包。 Retrying

                    它是用 Python 编写的,用于简化向几乎任何事情添加重试行为的任务。

                    【讨论】:

                      【解决方案15】:

                      使用递归

                      for i in range(100):
                          def do():
                              try:
                                  ## Network related scripts
                              except SpecificException as ex:
                                  do()
                          do() ## invoke do() whenever required inside this loop
                      

                      【讨论】:

                      • 退出条件?还是运行 100 * infinity?
                      【解决方案16】:

                      使用 while 和计数器:

                      count = 1
                      while count <= 3:  # try 3 times
                          try:
                              # do_the_logic()
                              break
                          except SomeSpecificException as e:
                              # If trying 3rd time and still error?? 
                              # Just throw the error- we don't have anything to hide :)
                              if count == 3:
                                  raise
                              count += 1
                      

                      【讨论】:

                        【解决方案17】:

                        这里有一个和其他类似的解决方案,但是如果没有达到规定的次数或者重试,就会引发异常。

                        tries = 3
                        for i in range(tries):
                            try:
                                do_the_thing()
                            except KeyError as e:
                                if i < tries - 1: # i is zero indexed
                                    continue
                                else:
                                    raise
                            break
                        

                        【讨论】:

                        • 不错的答案,但变量名 retries 具有误导性。它应该是tries
                        • 非常好的解决方案,谢谢。可以通过在每次尝试之间添加延迟来改进它。在处理 API 时非常有用。
                        【解决方案18】:

                        这是我关于如何解决此问题的想法:

                        j = 19
                        def calc(y):
                            global j
                            try:
                                j = j + 8 - y
                                x = int(y/j)   # this will eventually raise DIV/0 when j=0
                                print("i = ", str(y), " j = ", str(j), " x = ", str(x))
                            except:
                                j = j + 1   # when the exception happens, increment "j" and retry
                                calc(y)
                        for i in range(50):
                            calc(i)
                        

                        【讨论】:

                        • 这太离谱了。
                        【解决方案19】:

                        在您的 for 循环中执行 while True,将您的 try 代码放入其中,并仅在您的代码成功时才从该 while 循环中中断。

                        for i in range(0,100):
                            while True:
                                try:
                                    # do stuff
                                except SomeSpecificException:
                                    continue
                                break
                        

                        【讨论】:

                        • @Ignacio,continue 重试while 循环,当然,不是 for (!),所以i 不是“下一个”任何东西——它是当然,与同一 while 的前一个(失败的)分支完全相同。
                        • 正如 xorsyst 所指出的,建议在此处设置重试限制。否则你可能会在很长一段时间内陷入循环。
                        • 这是一个很好的例子:medium.com/@echohack/…
                        • 我肯定会省略 while True: 行,否则 break 会继续外循环到筋疲力尽。
                        • @Sankalp,在我看来这个答案适合问题文本。
                        【解决方案20】:

                        最清晰的方法是显式设置i。例如:

                        i = 0
                        while i < 100:
                            i += 1
                            try:
                                # do stuff
                        
                            except MyException:
                                continue
                        

                        【讨论】:

                        • 是 C 还是 C++?我说不出来。
                        • @Georg 那是 Python,如问题中所述。或者你出于某种原因在哪里讽刺?
                        • 这不符合 OP 的要求。如果您将i += 1 放在# do stuff 之后,可能会发生这种情况。
                        • 不是pythonic。这种东西应该使用range
                        • 我同意,这绝对应该使用范围。
                        【解决方案21】:

                        Python Decorator Library 中有类似的东西。

                        请记住,它不测试异常,而是返回值。它会重试,直到修饰函数返回 True。

                        稍作修改的版本应该可以解决问题。

                        【讨论】:

                        【解决方案22】:

                        我更喜欢限制重试次数,这样如果该特定项目出现问题,您最终会继续下一个项目,因此:

                        for i in range(100):
                          for attempt in range(10):
                            try:
                              # do thing
                            except:
                              # perhaps reconnect, etc.
                            else:
                              break
                          else:
                            # we failed all the attempts - deal with the consequences.
                        

                        【讨论】:

                        • @g33kz0r 如果 for 循环没有中断,Python 中的 for-else 结构会执行 else 子句。因此,在这种情况下,如果我们尝试了所有 10 次尝试并且总是得到一个异常,那么该部分就会执行。
                        • 这是一个很好的答案!真的值得更多的支持。它完美地使用了 Python 中的所有功能,尤其是 for 中鲜为人知的 else: 子句。
                        • 在 try: 部分结束时您不需要休息一下吗?通过 try: 中的额外中断,如果该过程成功完成,则循环将中断,如果未成功完成,它将直接进入异常部分。那有意义吗?如果我在尝试结束时不休息:它只会做 100 次。
                        • @Tristan - tryelse 子句执行您正在寻找的“如果成功,则中断”。
                        • 我也更喜欢 for 循环来重试。这段代码的一个问题是,如果你想在放弃尝试时重新引发异常,你需要在 except 子句中使用类似“if attempt=9: raise”的内容,并记住使用 9 而不是 10 .
                        【解决方案23】:

                        不使用那些丑陋的while循环的更“实用”的方法:

                        def tryAgain(retries=0):
                            if retries > 10: return
                            try:
                                # Do stuff
                            except:
                                retries+=1
                                tryAgain(retries)
                        
                        tryAgain()
                        

                        【讨论】:

                        • 很抱歉,但它似乎比“丑陋的 while 循环”变体丑得多;而且我喜欢函数式编程……
                        • 你需要确保你不会深度递归 - Python 中的默认堆栈大小是 1000
                        • 如果这将是“功能性的”,那么递归应该是:except: tryAgain(retries+1)
                        • 这样做的问题是我们需要将错误作为变量传递。
                        • 递归也有1000个限制
                        【解决方案24】:

                        仅当 try 子句成功时才增加循环变量

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2022-11-03
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多