异常后如何重试?

Fur*_*lon 220 python loops exception try-except

我有一个循环开始for i in range(0, 100).通常它运行正常,但有时它会因网络状况而失败.目前我设置它以便在失败时,它将continue在except子句中(继续到下一个数字i).

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

zne*_*eak 337

while True在你的for循环内部执行,将try代码放入其中,并while在代码成功时从该循环中断.

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

  • 除了使用`SomeSpecificError:`而不是捕获所有异常. (38认同)
  • @Ignacio,**呵呵?`continue`重试`while`循环,当然,**不是'for`(!),所以`i`不是**"下一个"任何东西 - 它与它完全相同当然,在同一个`while`的前一个(失败的)腿上. (27认同)
  • 正如xorsyst指出的那样,建议在那里设置重试限制.否则你可能会在很长一段时间内陷入循环. (11认同)
  • @Sankalp,在我看来,这个答案对于问题文本来说是正确的。 (4认同)
  • 尽管所提问题的答案是正确的,但大多数人会被第一个“for”循环和随后的无限“while”感到困惑。如果您想要重试失败的尝试 x 次,那么单个 for `for else` 循环可能就是您想要的。 (3认同)
  • 这是一个很好的例子:https://medium.com/@echohack/patterns-with-python-poll-an-api-832173a03e93 (2认同)
  • 我肯定会省略一会儿True:行,否则中断将继续使外部循环耗尽。 (2认同)

xor*_*yst 165

我倾向于限制重试次数,所以,如果有与特定项目,你最终会延续到下一个,这样一个问题:

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.
Run Code Online (Sandbox Code Playgroud)

  • 这是一个很好的答案!真的值得更多的赞成.它完美地使用了Python中的所有工具,尤其是`for`中鲜为人知的`else:`子句. (7认同)
  • @g33kz0r 如果 for 循环没有中断,Python 中的 for-else 构造将执行 else 子句。因此,在这种情况下,如果我们尝试所有 10 次尝试并始终得到异常,则该部分将执行。 (4认同)
  • @Tristan - `try` 的 `else` 子句执行您正在寻找的“如果成功,则中断”。 (3认同)
  • 在try:part的结尾不需要休息吗?在try:中附加中断,如果该过程成功完成,则循环将中断,如果未成功完成,则将直接转到异常部分。那有意义吗?如果我在尝试结束时没有休息:它只会做100次。 (2认同)
  • 我也更喜欢使用 for 循环来重试。这段代码中的一个问题是,如果你想在放弃尝试时重新引发异常,你需要在 ` except` 子句中使用类似 "if attempts=9: raise" 的内容,并记住使用 9 而不是 10 。 (2认同)

gon*_*eri 60

重试包是重试的代码块失败的好办法.

例如:

@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")
Run Code Online (Sandbox Code Playgroud)

  • 更一般地说,pypi有多个重试装饰器包:https://pypi.python.org/pypi?%3Aaction = search&term = retry + decorator&subsmit = search (4认同)
  • 据我了解,没有维护,更活跃的fork是https://github.com/jd/tenacity,也许也可以使用https://github.com/litl/backoff。 (3认同)
  • 自 2022 年 9 月 3 日起,重试包在新的维护者的带领下再次生效。 (3认同)

The*_*erk 19

这是一个类似于其他解决方案的解决方案,但如果在规定的数量或重试中没有成功,它将引发异常.

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
Run Code Online (Sandbox Code Playgroud)

  • 不错的答案,但变量名称“重试”具有误导性。它更应该是“尝试”。 (2认同)
  • 非常好的解决方案谢谢。可以通过在每次尝试之间添加延迟来改进。在处理 API 时非常有用。 (2认同)

小智 15

没有使用那些丑陋的while循环的更"功能"的方法:

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

tryAgain()
Run Code Online (Sandbox Code Playgroud)

  • 对不起,但它似乎比"丑陋的循环"变种更加丑陋; 而且我喜欢函数式编程...... (11认同)
  • 您需要确保不要深入递归 - Python中的默认堆栈大小为1000 (9认同)
  • 如果这将是'功能',递归应该是:`except:tryAgain(retries + 1)` (5认同)

np8*_*np8 14

替代品retryingtenacitybackoff(2020 年更新)

重试库是以前的路要走,但可悲的是它有一些缺陷,自2016年其他选择似乎还没有得到任何更新补偿坚韧。在写这篇文章的时候,坚韧有更多的 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 Code Online (Sandbox Code Playgroud)

上面的代码输出如下:

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.
.
.
.
Run Code Online (Sandbox Code Playgroud)

更多设置tenacity.retrytenacity GitHub 页面上列出。


Tom*_*ilä 9

最明确的方法是明确设置i.例如:

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

    except MyException:
        continue
Run Code Online (Sandbox Code Playgroud)

  • 是C还是C++?我说不出来. (35认同)
  • 不是pythonic.对于这种东西应该使用`range`. (4认同)
  • @Georg这是Python,正如问题中所述.或者你出于某种原因讽刺的地方? (3认同)
  • 这不符合OP的要求.如果你把'i + = 1`放在`#doo stuff'之后. (2认同)
  • 我同意,这绝对应该使用范围。 (2认同)

n8h*_*rie 8

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
Run Code Online (Sandbox Code Playgroud)

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


Mic*_*ael 5

Python Decorator Library中有类似的东西。

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

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

  • 以下是如何修改异常http://www.saltycrane.com/blog/2009/11/trying-out-retry-decorator-python/ (2认同)

Lau*_*RTE 5

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

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)
Run Code Online (Sandbox Code Playgroud)

用法:

for retry in onerror_retry(SomeSpecificException, do_stuff):
    retry()
Run Code Online (Sandbox Code Playgroud)


Ran*_*u R 5

使用 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
Run Code Online (Sandbox Code Playgroud)


Jos*_*mas 5

使用递归

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


Vor*_*man 5

attempts = 3
while attempts:
  try:
     ...
     ...
     <status ok>
     break
  except:
    attempts -=1
else: # executed only break was not  raised
   <status failed>
Run Code Online (Sandbox Code Playgroud)


小智 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
Run Code Online (Sandbox Code Playgroud)

一些用法示例:

@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
Run Code Online (Sandbox Code Playgroud)
@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
Run Code Online (Sandbox Code Playgroud)
> retry.retry(lambda a,b:a+b, 1, 2, times=2)

3
Run Code Online (Sandbox Code Playgroud)
> retry.retry(lambda a,b:a+b, 1, "2", times=2)

TypeError: unsupported operand type(s) for +: 'int' and 'str'
Run Code Online (Sandbox Code Playgroud)