Jak ponowić próbę po wyjątku?


252

Mam pętlę zaczynającą się od for i in range(0, 100). Zwykle działa poprawnie, ale czasami zawodzi z powodu warunków sieciowych. Obecnie mam to ustawione tak, że w przypadku awarii będzie continuew klauzuli wyjątku (przejdź do następnej liczby dla i).

Czy mogę ponownie przypisać ten sam numer ii ponownie przejść przez nieudaną iterację pętli?


1
Możesz używać range(100)bez pierwszego parametru. Jeśli używasz Python 2.x, którego możesz nawet użyć xrange(100), generuje iterator i zużywa mniej pamięci. (Nie chodzi o to, że liczy się tylko 100 obiektów.)
Georg Schölly,


2
istnieje bardzo eleganckie rozwiązanie z wykorzystaniem dekoratorów z obsługą arbitralnych wyjątków w tym wątku
zitroneneis

Odpowiedzi:


378

Wykonaj while Truewewnątrz pętli for, umieść trykod w środku i zrywaj z whilepętli tylko wtedy, gdy kod się powiedzie.

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

30
@Ignacio, co ? continueponawia whilepętla, oczywiście, niefor , tak (!) ijest nie „następny” coś - to jest dokładnie taki sam, jak to było na poprzedniej (nieudanej) noga sama while, oczywiście.
Alex Martelli,

13
Jak zauważa xorsyst, wskazane jest nałożenie na nie limitu ponownych prób. W przeciwnym razie możesz utknąć w pętli przez dłuższy czas.
Brad Koch

2
To doskonały przykład: medium.com/@echohack/…
Tony Melony,

7
Zdecydowanie pominąłbym linię True: w przeciwnym razie przerwa kontynuowałaby zewnętrzną pętlę do wyczerpania.
stycznia

1
@Sankalp, wydaje mi się, że ta odpowiedź jest właściwa dla tekstu pytania.
zneak

188

Wolę ograniczyć liczbę ponownych prób, aby w przypadku problemu z tym konkretnym elementem ostatecznie przejść do następnego, a zatem:

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 konstrukcja for-else w Pythonie wykonuje klauzulę else, jeśli pętla for nie ulegnie awarii. Tak więc w tym przypadku ta sekcja jest wykonywana, jeśli spróbujemy wszystkich 10 prób i zawsze otrzymamy wyjątek.
xorsyst

7
To świetna odpowiedź! Naprawdę zasługuje na znacznie więcej pozytywnych opinii. Doskonale wykorzystuje wszystkie udogodnienia w Pythonie, zwłaszcza mniej znaną else:klauzulę for.
pepoluan

2
Nie potrzebujesz przerwy na końcu próby: część? Z dodatkową przerwą w try :, jeśli proces zakończy się pomyślnie, pętla się przerwie, jeśli nie zakończy się pomyślnie, przejdzie bezpośrednio do części wyjątku. Czy to ma sens? Jeśli nie zrobię przerwy pod koniec próby: robi to po prostu 100 razy.
Tristan

1
@Tristan - elseklauzula ta tryrobi to „jeśli się powiedzie, a następnie złam ”, którego szukasz.
PaulMcG,

1
Wolę też pętlę for do ponownej próby. Zmarszczenie w tym kodzie polega na tym, że jeśli chcesz ponownie zgłosić wyjątek, gdy rezygnujesz z próbowania, potrzebujesz czegoś takiego jak „if próba = 9: podbicie” wewnątrz exceptklauzuli i pamiętaj, aby użyć 9, a nie 10.
PaulMcG

69

Pakiet Ponowna próba jest to dobry sposób, aby ponowić próbę blok kodu na niepowodzenie.

Na przykład:

@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
Mówiąc bardziej ogólnie, pypi ma wiele pakietów dla ponownych
kert 16.04.16

czy istnieje możliwość wydrukowania numeru próby ponownej próby za każdym razem, gdy się nie powiedzie?
dim_user,

8
Jak zrozumiałem, nie jest utrzymywane, bardziej aktywny widelec to github.com/jd/tenacity i być może github.com/litl/backoff może być również używany.
Alexey Shrub

22

Oto rozwiązanie podobne do innych, ale podniesie wyjątek, jeśli nie powiedzie się w przepisanej liczbie lub próbach.

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

Dobra odpowiedź, ale nazwa zmiennej retrieswprowadza w błąd. Tak powinno być tries.
Lukas,

Prawda @Lukas. Naprawiony.
TheHerk

Bardzo dobre rozwiązanie, dziękuję. Można to poprawić, dodając opóźnienie między każdą próbą. Bardzo przydatne w przypadku interfejsów API.
Sam

14

Bardziej „funkcjonalne” podejście bez użycia tych brzydkich pętli while:

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

tryAgain()

13
Przykro mi, ale wydaje się o wiele brzydsze niż warianty „brzydkie podczas pętli”; i lubię programowanie funkcjonalne ...
lvella,

9
Musisz jednak upewnić się, że nie powtórzysz głęboko - domyślny rozmiar stosu w Pythonie to 1000
Cal Paterson

5
Jeśli ma to być „funkcjonalne”, rekurencja powinna wyglądać następująco:except: tryAgain(retries+1)
quamrana

Problem polega na tym, że musimy przekazywać błąd jako zmienne.
lowzhao

11

Najwyraźniejszym sposobem byłoby jawne ustawienie i. Na przykład:

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

    except MyException:
        continue

37
Czy to C czy C ++? Nie umiem powiedzieć
Georg Schölly,

5
@ Georg To jest Python, jak stwierdzono w pytaniu. Lub gdzie z jakiegoś powodu jesteś sarkastyczny?
Jakob Borg,

2
To nie robi tego, o co poprosił PO. Może, jeśli wstawisz i += 1zaraz po # do stuff.
fmalina,

5
Nie pytoniczny. Powinien używać rangedo tego rodzaju rzeczy.
Mystic

2
Zgadzam się, zdecydowanie powinien użyć zasięgu.
user2662833

5

Ogólne rozwiązanie z limitem czasu:

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)

Stosowanie:

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

Czy można określić osobną funkcję do sprawdzania błędów? except exception:
Pobiera dane

Zamiast try … exceptmożesz użyć ifinstrukcji. Ale jest mniej pytoniczny.
Laurent LAPORTE,

To rozwiązanie nie działa. trinket.io/python/caeead4f6b Wyjątek zgłoszony przez do_stuff nie powoduje bąbelkowania w generatorze. Dlaczego tak by było? Do_stuff jest wywoływany w treści pętli for, która sama jest na zewnętrznym poziomie, a nie zagnieżdżona w generatorze.
isarandi

Masz rację, ale z innego powodu: callbackfunkcja nigdy nie jest wywoływana. Zapomniałem nawiasu, zastąp przez callback().
Laurent LAPORTE

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

Moja wersja jest podobna do kilku powyższych, ale nie używa osobnej whilepętli i ponownie podnosi ostatni wyjątek, jeśli wszystkie próby się nie powiodą. Można jawnie ustawić err = Nonena górze, ale nie jest to absolutnie konieczne, ponieważ powinien wykonać ostatni elseblok tylko wtedy, gdy wystąpił błąd i dlatego errjest ustawiony.



4

Korzystanie z rekurencji

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
Wyjść z warunku? Czy to działa 100 * nieskończoność?
ingyhere

3

Używanie while i licznika:

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

Możesz użyć pakietu ponownej próby w Pythonie. Ponowna próba

Jest napisany w Pythonie, aby uprościć zadanie dodawania zachowania ponownych prób do niemal wszystkiego.


2

Alternatywy dla retrying: tenacityi backoff(aktualizacja 2020)

Ponawianie biblioteka była poprzednio do zrobienia, ale niestety ma kilka błędów i nie ma żadnych nowości od 2016. Inne alternatywy wydają się być ograniczania i wytrzymałości . W czasie pisania tego upór miał więcej gwiazd GItHub (2,3k vs 1,2k) i został zaktualizowany niedawno, dlatego postanowiłem go użyć. Oto przykład:

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()

Powyższy kod wyświetla coś takiego:

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.
.
.
.

Więcej ustawień dla tych tenacity.retrymożna znaleźć na stronie GitHub poświęconej wytrzymałości .


1

Jeśli chcesz rozwiązania bez zagnieżdżonych pętli i powoływania się breakna sukces, możesz opracować szybkie opakowanie retriabledla dowolnej iterowalnej. Oto przykład problemu z siecią, na który często wpadam - zapisane uwierzytelnianie wygasa. Jego użycie brzmiałoby następująco:

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

Używam następujących w moich kodach,

   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

Oto moje zdanie na ten temat. Następująca retryfunkcja obsługuje następujące funkcje:

  • Zwraca wartość wywołanej funkcji, gdy się powiedzie
  • Podnosi wyjątek wywołanej funkcji, jeśli próby zostaną wyczerpane
  • Limit liczby prób (0 dla nieograniczonej)
  • Poczekaj (liniowo lub wykładniczo) między próbami
  • Spróbuj ponownie tylko wtedy, gdy wyjątek jest instancją określonego typu wyjątku.
  • Opcjonalne rejestrowanie prób
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

Stosowanie:

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)

Zobacz mój post, aby uzyskać więcej informacji.


-2

Oto mój pomysł, jak to naprawić:

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
To jest daleko od bazy.
Chris Johnson,

-2

Niedawno pracowałem z moim pytonem nad rozwiązaniem tego problemu i chętnie podzielę się nim z gośćmi stosu przepływów. W razie potrzeby przekaż opinię.

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}')

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.