Как повторить попытку после исключения?

252

У меня есть цикл, начинающийся с for i in range(0, 100). Обычно он работает правильно, но иногда происходит сбой из-за условий сети. В настоящее время он настроен таким образом, что в случае сбоя он будет continueуказан в условии "исключение" (перейдите к следующему номеру для i).

Могу ли я переназначить тот же номер и снова iвыполнить неудачную итерацию цикла?

FurtiveFelon
источник
1
Вы можете использовать range(100)без первого параметра. Если вы используете Python 2.x, который вы могли бы использовать xrange(100), он генерирует итератор и использует меньше памяти. (Не то, чтобы это
имело
2
есть очень элегантное решение, использующее декораторы с поддержкой обработки произвольных исключений в этом потоке
zitroneneis

Ответы:

380

Сделайте while Trueвнутри вашего цикла for, поместите свой tryкод внутрь, и выходите из этого whileцикла только тогда, когда ваш код завершится успешно.

for i in range(0,100):
    while True:
        try:
            # do stuff
        except SomeSpecificException:
            continue
        break
zneak
источник
30
@ Игнасио, а ? continueповторит whileцикл, конечно, неfor , так (!) iэто не «следующий» ничего - это точно так же , как это было на предыдущей (неудачной) нога одного и того же while, конечно.
Алекс Мартелли
13
Как отмечает xorsyst, желательно установить ограничение на количество попыток. В противном случае вы можете застрять в петле довольно долго.
Брэд Кох
2
Это отличный пример: medium.com/@echohack/…
Тони Мелони
7
Я бы определенно пропустил строку while True: иначе перерыв продолжит внешний цикл до истощения.
января
1
@Sankalp, мне кажется, что этот ответ подходит для текста вопроса.
zneak
189

Я предпочитаю ограничить количество повторных попыток, чтобы в случае возникновения проблемы с этим конкретным элементом вы в конечном итоге переходили к следующему, таким образом:

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.
xorsyst
источник
3
@ g33kz0r конструкция for-else в Python выполняет предложение else, если цикл for не прерывается. Таким образом, в этом случае этот раздел выполняется, если мы попробуем все 10 попыток и всегда получим исключение.
xorsyst
7
Это отличный ответ! Действительно заслуживает гораздо больше голосов. Он прекрасно использует все возможности Python, особенно менее известный else:пункт for.
pepoluan
2
Вам не нужен перерыв в конце попытки: часть? С дополнительным разрывом в try :, если процесс завершается успешно, цикл прерывается, если он не завершается успешно, он сразу переходит к части исключения. Имеет ли это смысл? Если я не ставлю перерыв в конце попытки: он просто делает это 100 раз.
Тристан
1
@Tristan - elseпункт tryделает это "если успешно, то сломай", что вы ищете.
PaulMcG
1
Я также предпочитаю петлю для повторной попытки. Проблема в этом коде заключается в том, что если вы хотите повторно вызвать исключение при отказе от попыток, вам нужно что-то вроде «если попытка = 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")
goneri
источник
4
В общем, pypi имеет несколько пакетов для повторных
kert
есть ли в любом случае вы можете напечатать номер повторной попытки каждый раз, когда она не удалась?
dim_user
8
Как я понял, не поддерживается, более активным форком является github.com/jd/tenacity и, возможно, также можно использовать github.com/litl/backoff .
Алексей Куст
23

Вот решение, подобное другим, но оно вызовет исключение, если оно не преуспеет в предписанном количестве или повторных попытках.

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
TheHerk
источник
Хороший ответ, но имя переменной retriesвводит в заблуждение. Это должно быть гораздо лучше tries.
Лукас
Правда @Lukas. Исправлена.
TheHerk
Очень хорошее решение, спасибо. Это можно улучшить, добавив задержку между каждой попыткой. Очень полезно при работе с API.
Сэм
14

Более «функциональный» подход без использования этих уродливых циклов while:

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

tryAgain()
restbeckett
источник
13
Я извиняюсь, но это кажется намного более уродливым чем варианты "уродливых циклов while"; и я
увлекаюсь
9
Вы должны убедиться, что не выполняете глубокие рекурсии - размер стека по умолчанию в Python равен 1000
Cal Paterson
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 ++? Я не могу сказать.
Георг Шолли
5
@ Georg Это Python, как указано в вопросе. Или где ты саркастичен по какой-то причине?
Якоб Борг
2
Это не делает то, что просил ОП. Это может быть, если вы положите i += 1сразу после # do stuff.
Фмалина
5
Не питонический. Следует использовать rangeдля такого рода вещей.
Мистик
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:
Пратик Хадлоя
Вместо try … exceptвы можете использовать ifутверждение. Но это менее питонно.
Лоран Лапорт
Это решение не работает. trinket.io/python/caeead4f6b Исключение, генерируемое do_stuff, не передается генератору. Во всяком случае, зачем это? do_stuff вызывается в теле цикла for, который находится на внешнем уровне сам по себе, а не в генераторе.
Изаранди
Ваше право, но по другой причине: 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он установлен.

n8henrie
источник
4

В библиотеке Python Decorator есть нечто похожее .

Пожалуйста, имейте в виду, что это не проверка исключений, а возвращаемое значение. Повторяется до тех пор, пока оформленная функция не вернет True.

Немного измененная версия должна сделать свое дело.

Майкл
источник
Вот как можно изменить его для исключений saltycrane.com/blog/2009/11/try-out-retry-decorator-python
Энтони Дениер
4

Используя 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
Ранджу Р
источник
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

Вы можете использовать пакет повторных попыток Python. Повторная попытка

Он написан на Python, чтобы упростить задачу добавления поведения повтора практически ко всему.

ManJan
источник
2

Альтернативы retrying: tenacityи backoff(обновление 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 .

NP8
источник
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
HS Rathore
источник
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)

Смотрите мой пост для получения дополнительной информации.

dux2
источник
-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}')
Рашад Кабир
источник
-9

увеличивайте переменную цикла только тогда, когда предложение try успешно

appusajeev
источник