Wie versuche ich es nach einer Ausnahme erneut?


252

Ich habe eine Schleife beginnend mit for i in range(0, 100). Normalerweise läuft es korrekt, aber manchmal schlägt es aufgrund von Netzwerkbedingungen fehl. Derzeit habe ich es so eingestellt, dass es bei einem Fehler continuein der Ausnahmeklausel angezeigt wird (weiter mit der nächsten Nummer für i).

Kann ich die gleiche Nummer neu zuweisen iund die fehlgeschlagene Iteration der Schleife erneut durchlaufen?


1
Sie können range(100)ohne den ersten Parameter verwenden. Wenn Sie Python 2.x verwenden, das Sie sogar verwenden könnten xrange(100), wird ein Iterator generiert und weniger Speicher benötigt. (Nicht, dass es bei nur 100 Objekten wichtig wäre.)
Georg Schölly


2
Es gibt eine sehr elegante Lösung, bei der Dekorateure mit Unterstützung für den Umgang mit willkürlichen Ausnahmen in diesem Thread verwendet werden
zitroneneis

Antworten:


378

Führen Sie eine while Truefor-Schleife durch, fügen Sie Ihren tryCode ein und brechen Sie diese whileSchleife erst ab, wenn Ihr Code erfolgreich ist.

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

30
@Ignacio, nicht wahr ? continuewiederholt die whileSchleife natürlich nicht die for(!), iist also nichts "das nächste" - es ist genau das gleiche wie auf einem vorherigen (fehlgeschlagenen) Abschnitt desselben while.
Alex Martelli

13
Wie Xorsyst feststellt, ist es ratsam, dort ein Wiederholungslimit festzulegen. Andernfalls könnten Sie für einige Zeit in einer Schleife stecken bleiben.
Brad Koch

2
Dies ist ein hervorragendes Beispiel: medium.com/@echohack/…
Tony Melony

7
Ich würde definitiv die while True: line weglassen, sonst wird die Pause die äußere Schleife bis zur Erschöpfung fortsetzen.
Jan

1
@ Sankalp, es scheint mir, dass diese Antwort für den Fragentext richtig ist.
Zneak

188

Ich ziehe es vor, die Anzahl der Wiederholungsversuche zu begrenzen, damit Sie, wenn es ein Problem mit diesem bestimmten Element gibt, schließlich mit dem nächsten fortfahren, also:

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 Das for-else-Konstrukt in Python führt die else-Klausel aus, wenn die for-Schleife nicht unterbrochen wird. In diesem Fall wird dieser Abschnitt ausgeführt, wenn wir alle 10 Versuche versuchen und immer eine Ausnahme erhalten.
Xorsyst

7
Das ist eine großartige Antwort! Verdient wirklich viel mehr Gegenstimmen. Es nutzt perfekt alle Funktionen in Python, insbesondere die weniger bekannte else:Klausel von for.
Pepoluan

2
Benötigen Sie am Ende des Versuchs keine Pause: Teil? Mit der zusätzlichen Unterbrechung in try: Wenn der Prozess erfolgreich abgeschlossen wird, wird die Schleife unterbrochen. Wenn sie nicht erfolgreich abgeschlossen wird, geht sie direkt zum Ausnahmeteil. Ist das sinnvoll? Wenn ich am Ende des Versuchs keine Pause mache: Es macht das Ding nur 100 Mal.
Tristan

1
@Tristan - die elseKlausel von trymacht dies "Wenn erfolgreich, dann brechen", die Sie suchen.
PaulMcG

1
Ich bevorzuge auch eine for-Schleife zum erneuten Versuchen. Ein Fehler in diesem Code ist, dass Sie, wenn Sie die Ausnahme erneut except
auslösen

69

Das Wiederholungspaket ist eine gute Möglichkeit, einen Codeblock bei einem Fehler erneut zu versuchen .

Beispielsweise:

@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
Im Allgemeinen hat pypi mehrere Pakete für Wiederholungsdekorateure: pypi.python.org/…
kert

Gibt es überhaupt eine Möglichkeit, die Nummer des Wiederholungsversuchs jedes Mal auszudrucken, wenn er fehlschlägt?
dim_user

8
Soweit ich weiß, wird nicht gewartet, aktivere Gabel ist github.com/jd/tenacity und vielleicht kann github.com/litl/backoff auch verwendet werden.
Alexey Shrub

23

Hier ist eine ähnliche Lösung wie bei anderen, die jedoch die Ausnahme auslöst, wenn die vorgeschriebene Anzahl nicht erreicht wird oder es erneut versucht wird.

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

Schöne Antwort, aber der Variablenname retriesist irreführend. Es sollte viel lieber sein tries.
Lukas

True @Lukas. Fest.
TheHerk

Sehr gute Lösung, danke. Es könnte verbessert werden, indem zwischen jedem Versuch eine Verzögerung hinzugefügt wird. Sehr nützlich beim Umgang mit APIs.
Sam

14

Der "funktionalere" Ansatz, ohne diese hässlichen while-Schleifen zu verwenden:

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

tryAgain()

13
Es tut mir leid, aber es scheint viel hässlicher als die "hässlichen while-Schleifen" -Varianten; und ich
mag

9
Sie müssen jedoch sicherstellen, dass Sie nicht tief zurückgreifen - die Standardstapelgröße in Python ist 1000
Cal Paterson

5
Wenn dies "funktional" sein soll, sollte die Rekursion sein:except: tryAgain(retries+1)
Quamrana

Das Problem dabei ist, dass wir Fehler als Variablen weitergeben müssen.
Lowzhao

11

Der klarste Weg wäre, explizit festzulegen i. Beispielsweise:

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

    except MyException:
        continue

37
Ist das C oder C ++? Ich kann es nicht sagen.
Georg Schölly

5
@Georg Das ist Python, wie in der Frage angegeben. Oder wo bist du aus irgendeinem Grund sarkastisch?
Jakob Borg

2
Dies macht nicht das, was das OP verlangt hat. Es könnte sein, wenn Sie i += 1kurz danach setzen # do stuff.
Fmalina

5
Nicht pythonisch. Sollte rangefür diese Art von Sachen verwenden.
Mystic

2
Ich bin damit einverstanden, dies sollte definitiv Reichweite verwenden.
user2662833

5

Eine generische Lösung mit Timeout:

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)

Verwendung:

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

Ist es möglich, eine separate Funktion für die Fehlerprüfung anzugeben? Es würde die Ausgabe des Rückrufs nehmen und an die Fehlerprüfungsfunktion übergeben, um zu entscheiden, ob es ein Fehler oder ein Erfolg war, anstatt eine einfacheexcept exception:
Pratik Khadloya

Anstelle von a können try … exceptSie eine ifAnweisung verwenden. Aber es ist weniger pythonisch.
Laurent LAPORTE

Diese Lösung funktioniert nicht. trinket.io/python/caeead4f6b Die von do_stuff ausgelöste Ausnahme sprudelt nicht zum Generator. Warum sollte es überhaupt? do_stuff wird im Hauptteil der for-Schleife aufgerufen, die sich auf einer äußeren Ebene befindet und nicht im Generator verschachtelt ist.
Isarandi

Ihr Recht, aber aus einem anderen Grund: Die callbackFunktion wird nie aufgerufen. Ich habe die Klammer vergessen, durch ersetzen 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

Meine Version ähnelt mehreren der oben genannten, verwendet jedoch keine separate whileSchleife und löst die neueste Ausnahme erneut aus, wenn alle Wiederholungsversuche fehlschlagen. Könnte explizit err = Noneoben gesetzt werden, ist aber nicht unbedingt erforderlich, da es den letzten elseBlock nur ausführen sollte, wenn ein Fehler aufgetreten errist und daher gesetzt ist.


4

In der Python Decorator-Bibliothek gibt es etwas Ähnliches .

Bitte beachten Sie, dass nicht auf Ausnahmen geprüft wird, sondern auf den Rückgabewert. Es wird wiederholt, bis die dekorierte Funktion True zurückgibt.

Eine leicht modifizierte Version sollte den Trick machen.



4

Verwenden von while und einem Zähler:

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

Rekursion verwenden

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
Ausgangsbedingung? Oder läuft das 100 * unendlich?
Ingyhere

3

Sie können das Python-Wiederholungspaket verwenden. Wiederholen

Es wurde in Python geschrieben, um das Hinzufügen von Wiederholungsverhalten zu nahezu allem zu vereinfachen.


2

Alternativen zu retrying: tenacityund backoff(Update 2020)

Die Wiederholungsbibliothek war früher der richtige Weg, hat aber leider einige Fehler und seit 2016 keine Updates mehr. Andere Alternativen scheinen Backoff und Hartnäckigkeit zu sein . Während des Schreibens hatte die Zähigkeit mehr GItHub-Sterne (2,3k gegenüber 1,2k) und wurde kürzlich aktualisiert, daher habe ich mich für die Verwendung entschieden. Hier ist ein Beispiel:

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

Der obige Code gibt ungefähr Folgendes aus:

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

Weitere Einstellungen für tenacity.retryfinden Sie auf der Seite mit der Hartnäckigkeit von GitHub .


1

Wenn Sie eine Lösung ohne verschachtelte Schleifen und breakmit Erfolg aufrufen möchten, können Sie einen schnellen Wrap retriablefür jedes iterable entwickeln. Hier ist ein Beispiel für ein Netzwerkproblem, auf das ich häufig stoße: Die gespeicherte Authentifizierung läuft ab. Die Verwendung würde so lauten:

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

Ich benutze folgendes in meinen Codes,

   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

Hier ist meine Meinung zu diesem Thema. Die folgende retryFunktion unterstützt die folgenden Funktionen:

  • Gibt den Wert der aufgerufenen Funktion zurück, wenn sie erfolgreich ist
  • Löst die Ausnahme der aufgerufenen Funktion aus, wenn die Versuche erschöpft sind
  • Begrenzung der Anzahl der Versuche (0 für unbegrenzt)
  • Warten Sie (linear oder exponentiell) zwischen den Versuchen
  • Wiederholen Sie diesen Vorgang nur, wenn die Ausnahme eine Instanz eines bestimmten Ausnahmetyps ist.
  • Optionale Protokollierung von Versuchen
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

Verwendung:

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)

Siehe meinen Beitrag für weitere Informationen.


-2

Hier ist meine Idee, wie man das behebt:

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
Dies ist weit weg von der Basis.
Chris Johnson

-2

Ich habe kürzlich mit meinem Python an einer Lösung für dieses Problem gearbeitet und teile sie gerne mit Stackoverflow-Besuchern. Bitte geben Sie Feedback, wenn dies erforderlich ist.

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

Erhöhen Sie Ihre Schleifenvariable nur, wenn die try-Klausel erfolgreich ist

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.