异常后如何重试?


251

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

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


1
您可以range(100)不使用第一个参数而使用。如果您使用Python 2.x,甚至可以使用xrange(100),这将生成一个迭代器并使用更少的内存。(这并不意味着只包含100个对象。)
GeorgSchölly10年


2
有一个非常优雅的解决方案,使用装饰器并支持处理该线程中的
畸变分泌物

Answers:


378

做一个while True内部的for循环,把你的try代码中,并突破从while只有当你的代码的成功循环。

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

30
@Ignacio,吗? continue重试while循环,当然不是for,所以(!)i不是 “下一个”什么-这是因为它是在同以前(失败)的腿完全一样的while,当然。
Alex Martelli 2010年

13
正如xorsyst所指出的,建议在此设置重试限制。否则,您可能会在相当长的一段时间内陷入循环。
布拉德·科赫

2
这是一个很好的例子:medium.com/@echohack/…–
Tony Melony

7
我肯定会省略一会儿True:行,否则中断将继续使外部循环耗尽。

1
@Sankalp,在我看来,这个答案适合于问题文本。
zneak

187

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

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.

3
@ g33kz0r如果for循环没有中断,Python中的for-else构造将执行else子句。因此,在这种情况下,如果我们尝试所有10次尝试并始终获得异常,则该部分将执行。
xorsyst 2015年

7
这是一个很好的答案!确实值得更多的支持。它完美地使用了Python中的所有功能,尤其是鲜为人知的else:子句for
pepoluan 2015年

2
在try:part的结尾不需要休息吗?在try:中附加中断,如果该过程成功完成,则循环将中断,如果未成功完成,则将直接转到异常部分。那有意义吗?如果我在尝试结束时没有休息:它只会做100次。
特里斯坦

1
@Tristan-的else子句try执行此操作,即“寻找成功,然后中断”。
PaulMcG '16

1
我也更喜欢使用for循环重试。在此代码中的皱纹是,如果你想重新引发异常,当你放弃尝试,你需要像“如果尝试= 9:提高”内部except条款,并记得用9,而不是10
PaulMcG

69

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

例如:

@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")

4
更一般而言,pypi具有多个可重试装饰器的程序包: pypi.python.org/…–
kert

无论如何,您每次失败都可以打印重试次数吗?
dim_user

8
据我了解,没有维护,更活跃的fork是github.com/jd/tenacity,也许也可以使用github.com/litl/backoff
阿列克谢灌木

22

这是一个与其他解决方案类似的解决方案,但是如果未按规定的次数或重试次数失败,则会引发异常。

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
卢卡斯

正确@Lukas。固定。
TheHerk

很好的解决方案,谢谢。可以通过在每次尝试之间增加延迟来改进它。处理API时非常有用。
山姆

14

没有使用那些难看的while循环的更“实用”的方法:

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

tryAgain()

13
抱歉,它看起来比“ ugly while loops”变体丑陋得多。我很喜欢函数式编程...
lvella

9
但是,您需要确保您不会对它们进行深入的了解-Python中的默认堆栈大小为1000
Cal Paterson 2014年

5
如果这将是“功能性的”,则递归应为:except: tryAgain(retries+1)
quamrana

问题是我们需要将错误作为变量传递。
lowzhao

11

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

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

    except MyException:
        continue

37
那是C还是C ++?我不知道
GeorgSchölly10年

5
@Georg那是Python,如问题所述。还是您出于某种原因感到讽刺?
雅各布·博格

2
这不符合OP的要求。如果您把它放在i += 1后面可能会的# do stuff
fmalina

5
不是pythonic。应该range用于这种东西。
Mystic 2014年

2
我同意,这绝对应该使用范围。
user2662833

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)

用法:

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

是否可以为错误检查指定单独的功能?它会使用回调的输出并传递给错误检查功能来确定是失败还是成功,而不是使用简单的方法except exception:
Pratik Khadloya 17-10-10

try … except可以使用if语句代替a 。但是它不是pythonic。
洛朗·拉波特

此解决方案不起作用。trinket.io/python/caeead4f6b do_stuff引发的异常不会冒泡到生成器。为什么会这样呢?在for循环的主体中调用do_stuff,该主体本身位于外部,而不嵌套在生成器中。
isarandi

您的权利,但出于另一个原因:callback从不调用该函数。我忘记了括号,取而代之callback()
洛朗·拉波特

5
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被设置时才执行最后一个块。



4

使用递归

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

1
退出条件?还是运行100 *无穷大?
ingyhere

3

使用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

3

您可以使用Python重试包。 重试

它是用Python编写的,以简化将重试行为添加到几乎所有内容的任务。


2

retryingtenacitybackoff(2020更新)的替代方案

重试库是以前的路要走,但可悲的是它有一些缺陷,自2016年其他选择似乎还没有得到任何更新补偿坚韧。在撰写本文时,坚韧程度更高的GItHub星星(2.3k和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()

上面的代码输出类似:

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韧度GitHub页面上列出了的更多设置。


1

如果您想要一个没有嵌套循环且无需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

1

我在代码中使用以下代码,

   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

0

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


0

这是我对这个问题的看法。以下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)

有关更多信息,请参见我的帖子


-2

这是我的解决方法:

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)

7
这是基础。
克里斯·约翰逊

-2

我最近与我的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}')

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.