In Python versuchen, bis kein Fehler mehr auftritt

74

Ich habe einen Code in Python, der wahrscheinlich einen Fehler verursacht, weil er auf einen Server zugreift und dieser Server manchmal einen internen Serverfehler von 500 aufweist. Ich möchte es so lange versuchen, bis ich den Fehler nicht mehr bekomme. Meine Lösung war:

while True:
    try:
        #code with possible error
    except:
         continue
    else:
         #the rest of the code
         break

Das scheint mir ein Hack zu sein. Gibt es einen pythonischeren Weg, dies zu tun?

murgatroid99
quelle
5
Äh ... was passiert, wenn der Remote-Server stirbt? Wird dies dort sitzen und 100% eines CPU-Kerns verbrauchen?
user9876
weiter sollte in sonst sein und einbrechen außer. Ist es ein Tippfehler?
Ankit Jaiswal
3
@aand: Nein. Wenn eine Ausnahme auftritt, möchte er es erneut versuchen (lesen :) continue, aber wenn keine Ausnahme auftritt, möchte er einige Dinge (durch einen Kommentar skizziert) ausführen und aus diesem seltsamen Missbrauch einer Schleife herauskommen. (wird elseausgeführt, wenn keine Ausnahme auftritt, ist das das fehlende Teil?)
1
Ein Wort der Warnung: Sie sollten einen gewissen Schutz vor dauerhaften Fehlern wie Schlafstörungen oder einer maximalen Versuchsbegrenzung hinzufügen. Andernfalls wird Ihr Prozess die CPU auf unbestimmte Zeit verbrauchen, wie @ user9876 feststellt.
Brad Koch
1
Für das, was es wert ist, gibt es weniger beängstigende Beispiele für dieselbe Anforderung. Ich versuche, ein Verzeichnis mit einem neuen Namen zu erstellen, daher werde ich eine Nummer an eine Basis anhängen und diese Nummer erhöhen, bis das Verzeichnis erfolgreich erstellt wurde. Ich werde nur den AlreadyExists-Fehler abfangen. Wenn mein Ordner nicht über unendlich viele Verzeichnisse verfügt, wird dies früher oder später eindeutig erfolgreich sein.
Unbeholfene Katze

Antworten:

79

Es wird nicht viel sauberer. Dies ist keine sehr saubere Sache. Bestenfalls (was ohnehin besser lesbar wäre, da die Bedingung für das breakoben mit dem ist while) könnten Sie eine Variable erstellen result = Noneund dabei eine Schleife ausführen is None. Sie sollten auch die Variablen anpassen und sie durch continuedie semantisch möglicherweise korrekten ersetzen pass(es ist Ihnen egal, ob ein Fehler auftritt, Sie möchten ihn nur ignorieren) und die löschen break- dies erhält auch den Rest des Codes, der nur einmal ausgeführt wird aus der Schleife. Beachten Sie auch, dass bloße except:Klauseln aus den in der Dokumentation angegebenen Gründen böse sind .

Beispiel mit allen oben genannten:

result = None
while result is None:
    try:
        # connect
        result = get_data(...)
    except:
         pass
# other code that uses result but is not involved in getting it
Benutzer, der kein Benutzer ist
quelle
7
Wenn es einen anhaltenden Grund dafür gibt, dass die Verbindung nicht erfolgreich ist, wird diese Lösung in einer Endlosschleife abgeschliffen.
Brad Koch
4
@BradKoch Natürlich. Das ist der Frage inhärent, und außerdem ist jede Korrektur (wie ein Gesamtzeitlimit oder eine begrenzte Anzahl von Versuchen) relativ orthogonal zu den von mir beschriebenen Änderungen.
4
Aber jede vorgeschlagene Antwort sollte sicher sein oder zumindest die Fallstricke beachten. Dies bietet keinen Schutz gegen 100% CPU-Verbrauch und gefährdet zukünftige Leser.
Brad Koch
@BradKoch Was würden Sie vorschlagen, um gegen 100% CPU-Auslastung zu stoppen, versuchen Sie es vielleicht 5-10 Mal, bevor Sie die Schleife beenden?
Manakin
1
@Datanovice Ja, ein einfaches Wiederholungslimit mit Verzögerung ist eine gute Lösung. Viele der anderen Antworten zeigen, wie das geht. Für komplizierte Szenarien wie das Wiederholen von HTTP-Anforderungen würde ich persönlich die Verwendung einer Bibliothek empfehlen .
Brad Koch
29

Hier ist eine, die nach 4 Versuchen schwer fehlschlägt und zwischen den Versuchen 2 Sekunden wartet. Ändern Sie, wie Sie möchten, was Sie von diesem wollen:

from time import sleep

for x in range(0, 4):  # try 4 times
    try:
        # msg.send()
        # put your logic here
        str_error = None
    except Exception as str_error:
        pass

    if str_error:
        sleep(2)  # wait for 2 seconds before trying to fetch the data again
    else:
        break

Hier ist ein Beispiel mit Backoff:

from time import sleep

sleep_time = 2
num_retries = 4
for x in range(0, num_retries):  
    try:
        # put your logic here
        str_error = None
    except Exception as str_error:
        pass

    if str_error:
        sleep(sleep_time)  # wait before trying to fetch the data again
        sleep_time *= 2  # Implement your backoff algorithm here i.e. exponential backoff
    else:
        break
radtek
quelle
6
Ich mag diese Antwort besser als andere, weil diese aufgrund der Schlaffunktion für andere Prozesse "nett" ist und auch nur begrenzte Versuche hat.
Ganesh Krishnan
25

Vielleicht so etwas:

connected = False

while not connected:
    try:
        try_connect()
        connected = True
    except ...:
        pass
Mouad
quelle
4
Jede vorgeschlagene Antwort sollte sicher sein oder zumindest die Fallstricke beachten. Dies bietet keinen Schutz gegen 100% CPU-Verbrauch und gefährdet zukünftige Leser.
Brad Koch
5

Das itertools.iter_exceptRezept fasst diese Idee zusammen, "eine Funktion wiederholt aufzurufen, bis eine Ausnahme ausgelöst wird". Es ähnelt der akzeptierten Antwort, aber das Rezept gibt stattdessen einen Iterator.

Aus den Rezepten:

def iter_except(func, exception, first=None):
    """ Call a function repeatedly until an exception is raised."""
    try:
        if first is not None:
            yield first()            # For database APIs needing an initial cast to db.first()
        while True:
            yield func()
    except exception:
        pass

Sie können den letzteren Code sicherlich direkt implementieren. Der Einfachheit halber verwende ich eine separate Bibliothek, more_itertoolsdie dieses Rezept für uns implementiert (optional).

Code

import more_itertools as mit

list(mit.iter_except([0, 1, 2].pop, IndexError))
# [2, 1, 0]

Einzelheiten

Hier wird die popMethode (oder gegebene Funktion) für jede Iteration des Listenobjekts aufgerufen, bis ein IndexErrorausgelöst wird.

In Ihrem Fall können Sie bei einigen connect_functionund erwarteten Fehlern einen Iterator erstellen, der die Funktion wiederholt aufruft, bis eine Ausnahme ausgelöst wird, z

mit.iter_except(connect_function, ConnectionError)

Behandeln Sie es an dieser Stelle wie jeden anderen Iterator, indem Sie es durchlaufen oder aufrufen next().

Pylang
quelle
Vielen Dank für den Hinweis auf iter_except. Diese Antwort wäre sogar noch besser, wenn ein Backoff-Ansatz implementiert würde, etwa wie folgt
Mellow-Yellow
5

Wenn Sie es aufgrund eines Fehlers erneut versuchen, sollten Sie immer:

  • Implementieren Sie ein Wiederholungslimit, oder Sie werden in einer Endlosschleife blockiert
  • Wenn Sie eine Verzögerung implementieren, werden Ressourcen zu stark beansprucht, z. B. Ihre CPU oder der bereits in Not geratene Remote-Server

Eine einfache generische Möglichkeit, dieses Problem zu lösen und gleichzeitig diese Probleme zu lösen, ist die Verwendung der Backoff- Bibliothek. Ein einfaches Beispiel:

import backoff

@backoff.on_exception(
    backoff.expo,
    MyException,
    max_tries=5
)
def make_request(self, data):
    # do the request

Dieser Code umschließt make_request mit einem Dekorator, der die Wiederholungslogik implementiert. Wir versuchen es immer wieder, wenn unser spezifischer Fehler MyExceptionauftritt, mit einem Limit von 5 Wiederholungsversuchen. Exponentielles Backoff ist in diesem Zusammenhang eine gute Idee, um die zusätzliche Belastung zu minimieren, die unsere Wiederholungsversuche für den Remote-Server bedeuten.

Brad Koch
quelle
3

Hier ist eine Dienstprogrammfunktion, die ich geschrieben habe, um den Wiederholungsversuch bis zum Erfolg in ein übersichtlicheres Paket zu packen. Es verwendet dieselbe Grundstruktur, verhindert jedoch Wiederholungen. Es könnte modifiziert werden, um die Ausnahme beim letzten Versuch relativ leicht zu fangen und erneut zu werfen.

def try_until(func, max_tries, sleep_time):
    for _ in range(0,max_tries):
        try:
            return func()
        except:
            sleep(sleep_time)
    raise WellNamedException()
    #could be 'return sensibleDefaultValue'

Kann dann so genannt werden

result = try_until(my_function, 100, 1000)

Wenn Sie Argumente übergeben müssen, my_functionkönnen Sie dies entweder tun, indem Sie try_untildie Argumente weiterleiten, oder indem Sie sie in ein Lambda ohne Argument einschließen:

result = try_until(lambda : my_function(x,y,z), 100, 1000)
Harry Harrison
quelle
2

Vielleicht Dekorateur basiert? Sie können als Dekorationsargumente eine Liste der Ausnahmen übergeben, für die wir es erneut versuchen möchten, und / oder die Anzahl der Versuche.

def retry(exceptions=None, tries=None):
    if exceptions:
        exceptions = tuple(exceptions)
    def wrapper(fun):
        def retry_calls(*args, **kwargs):
            if tries:
                for _ in xrange(tries):
                    try:
                        fun(*args, **kwargs)
                    except exceptions:
                        pass
                    else:
                        break
            else:
                while True:
                    try:
                        fun(*args, **kwargs)
                    except exceptions:
                        pass
                    else:
                        break
        return retry_calls
    return wrapper


from random import randint

@retry([NameError, ValueError])
def foo():
    if randint(0, 1):
        raise NameError('FAIL!')
    print 'Success'

@retry([ValueError], 2)
def bar():
    if randint(0, 1):
        raise ValueError('FAIL!')
    print 'Success'

@retry([ValueError], 2)
def baz():
    while True:
        raise ValueError('FAIL!')

foo()
bar()
baz()

Natürlich sollte der 'try'-Teil auf eine andere Funktion verschoben werden, da wir ihn in beiden Schleifen verwenden, aber es ist nur ein Beispiel;)

virhilo
quelle
Ein bisschen verspäteter Kommentar, aber die Verdoppelung des Codes könnte vermieden werden, indem "for _ in itertools.repeat (None, times = Versuche) verwendet wird:" Wenn "Versuche" "Keine" ist, wird die Schleife für immer fortgesetzt, aber wenn "Versuche" eine Zahl ist, es endet nach so vielen Iterationen.
Eldritch Cheese
2

Wie die meisten anderen würde ich empfehlen, eine begrenzte Anzahl von Malen zu versuchen und zwischen den Versuchen zu schlafen. Auf diese Weise befinden Sie sich nicht in einer Endlosschleife, falls dem Remote-Server tatsächlich etwas zustoßen sollte.

Ich würde auch empfehlen, nur fortzufahren, wenn Sie die spezifische Ausnahme erhalten, die Sie erwarten. Auf diese Weise können Sie weiterhin Ausnahmen behandeln, die Sie möglicherweise nicht erwarten.

from urllib.error import HTTPError
import traceback
from time import sleep


attempts = 10
while attempts > 0:
    try:
        #code with possible error
    except HTTPError:
        attempts -= 1
        sleep(1)
        continue
    except:
        print(traceback.format_exc())

    #the rest of the code
    break

Außerdem benötigen Sie keinen else-Block. Aufgrund der Fortsetzung im Ausnahmeblock überspringen Sie den Rest der Schleife, bis der try-Block funktioniert, die while-Bedingung erfüllt ist oder eine andere Ausnahme als HTTPError auftritt.

kein Name
quelle
2

Was ist mit der Wiederholungsbibliothek auf Pypi ? Ich benutze es seit einer Weile und es macht genau das, was ich will und mehr (bei Fehler erneut versuchen, bei Keine erneut versuchen, bei Zeitüberschreitung erneut versuchen). Unten ist ein Beispiel von ihrer Website:

import random
from retrying import retry

@retry
def do_something_unreliable():
    if random.randint(0, 10) > 1:
        raise IOError("Broken sauce, everything is hosed!!!111one")
    else:
        return "Awesome sauce!"

print do_something_unreliable()
David Tam
quelle
1
e = ''
while e == '':
    try:
        response = ur.urlopen('https://https://raw.githubusercontent.com/MrMe42/Joe-Bot-Home-Assistant/mac/Joe.py')
        e = ' '
    except:
        print('Connection refused. Retrying...')
        time.sleep(1)

Das sollte funktionieren. Es setzt e auf '' und die while-Schleife prüft, ob es noch '' ist. Wenn bei der try-Anweisung ein Fehler festgestellt wird, wird gedruckt, dass die Verbindung abgelehnt wurde, 1 Sekunde gewartet und dann von vorne begonnen. Es wird so lange fortgesetzt, bis beim Versuch kein Fehler mehr auftritt. Dadurch wird e auf '' gesetzt, wodurch die while-Schleife beendet wird.

user9311010
quelle
0

Hier ist ein kurzer Code, den ich verwende, um den Fehler als Zeichenfolge zu erfassen. Wird es erneut versuchen, bis es erfolgreich ist. Dies fängt alle Ausnahmen ab, aber Sie können dies nach Ihren Wünschen ändern.

start = 0
str_error = "Not executed yet."
while str_error:
    try:
        # replace line below with your logic , i.e. time out, max attempts
        start = raw_input("enter a number, 0 for fail, last was {0}: ".format(start))
        new_val = 5/int(start)
        str_error=None
    except Exception as str_error:
         pass

WARNUNG: Dieser Code bleibt in einer Forever-Schleife stecken, bis keine Ausnahme mehr auftritt. Dies ist nur ein einfaches Beispiel und möglicherweise müssen Sie früher aus der Schleife ausbrechen oder zwischen den Wiederholungsversuchen schlafen.

radtek
quelle
0

Ich versuche das jetzt, das habe ich mir ausgedacht;

    placeholder = 1
    while placeholder is not None:
        try:
            #Code
            placeholder = None
        except Exception as e:
            print(str(datetime.time(datetime.now()))[:8] + str(e)) #To log the errors
            placeholder = e
            time.sleep(0.5)
            continue
GrandadsJumper
quelle