Як повторити спробу після винятку?


252

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

Чи можна мені переписати те саме число iі знову пропустити невдалу ітерацію циклу?


1
Можна використовувати range(100)без першого параметра. Якщо ви використовуєте Python 2.x, ви навіть можете використовувати xrange(100), це генерує ітератор і використовує менше пам'яті. (Не те, що це стосується лише 100 об’єктів.)
Георг Шоллі


2
є дуже елегантне рішення з використанням декораторів із підтримкою для обробки довільних експедицій у цій темі
zitroneneis

Відповіді:


380

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

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

31
@Ignacio, так ? continueповторить whileцикл, звичайно, НЕfor , так (!) iце НЕ «наступний» нічого - це точно так же , як це було на попередній (невдалої) нога одного і того ж while, звичайно.
Алекс Мартеллі

13
Як зазначає xorsyst, доцільно поставити там обмеження для повторного спроби. Інакше ви можете зациклюватися на досить довгий час.
Бред Кох

2
Це прекрасний приклад: medium.com/@echohack/…
Тоні Мелоні

7
Я б точно не залишив рядки 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.

3
@ g33kz0r конструкція for-else в Python виконує статтю else, якщо цикл for не порушується. Отже, у цьому випадку цей розділ виконується, якщо ми спробуємо всі 10 спроб і завжди отримуємо виняток.
xorsyst

7
Це чудова відповідь! Дійсно заслуговує набагато більше грошей. Він прекрасно використовує всі засоби в Python, особливо менш відомий else:пункт for.
pepoluan

2
Вам не потрібна перерва в кінці спробу: частина? З додатковою перервою у спробі :, якщо процес успішно завершиться, цикл розірветься, якщо він не завершиться успішно, він перейде безпосередньо до частини винятку. Чи має це сенс? Якщо я не ставлю перерву в кінці спробу: це просто зробить річ 100 разів.
Трістан

1
@Tristan - elseзастереження " tryробить це", якщо успішно, то перерва ", яке ви шукаєте.
PaulMcG

1
Я також віддаю перевагу фор-петлі для повторної спроби. Зморшка в цьому коді полягає в тому, що якщо ви хочете повторно підняти виняток, коли ви відмовитесь від спроби, вам потрібно щось на зразок "if спроба = 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
Як я зрозумів, це не підтримується, більш активною вилкою є 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

Приємна відповідь, але назва змінної retriesвводить в оману. Це, скоріше, має бути tries.
Лукас

Правда @Lukas. Виправлено.
TheHerk

Дуже хороше рішення, дякую. Це можна покращити, додавши затримку між кожною спробою. Дуже корисно при роботі з API.
Сем

14

Більш "функціональний" підхід без використання цих потворних циклів:

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

tryAgain()

13
Вибачте, але це здається набагато потворніше, ніж варіанти "потворних, поки циклів"; і я захоплююсь функціональним програмуванням ...
lvella

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, як зазначено у питанні. Або де ти чомусь саркастичний?
Якоб Борг

3
Це не робить те, про що вимагала ОП. Можливо, якщо ви поставите i += 1відразу після # do stuff.
fmalina

5
Не пітонічний. Слід використовувати rangeдля такого роду речі.
Містик

2
Я згоден, для цього, безумовно, слід використовувати дальність.
користувач2662833

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встановлена.


4

У бібліотеці декораторів Python є щось подібне .

Зверніть увагу, що він не перевіряє винятки, а повертає значення. Він повторюється, поки оформлена функція не поверне значення True.

Трохи модифікована версія повинна зробити свою справу.


Ось, як змінити його на винятки saltycrane.com/blog/2009/11/trying-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, щоб спростити завдання додавати повторну поведінку майже ні до чого.


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 із завзятістю .


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

Ось мій погляд на це питання. Наступна 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

я нещодавно працював зі своїм пітоном над вирішенням цієї проблеми, і я радий поділитися ним із відвідувачами 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

збільшуйте змінну циклу лише тоді, коли пробний пункт успішний

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.