Gibt es eine pythonische Möglichkeit, etwas maximal maximal auszuprobieren? [Duplikat]

84

Ich habe ein Python-Skript, das einen MySQL-Server auf einem gemeinsam genutzten Linux-Host abfragt. Aus irgendeinem Grund geben Abfragen an MySQL häufig den Fehler "Server ist weg" zurück:

_mysql_exceptions.OperationalError: (2006, 'MySQL server has gone away')

Wenn Sie die Abfrage unmittelbar danach erneut versuchen, ist sie normalerweise erfolgreich. Ich würde gerne wissen, ob es in Python eine sinnvolle Möglichkeit gibt, eine Abfrage auszuführen, und wenn dies fehlschlägt, versuchen Sie es erneut, bis zu einer festgelegten Anzahl von Versuchen. Wahrscheinlich möchte ich, dass es 5 Mal versucht wird, bevor ich ganz aufgebe.

Hier ist die Art von Code, die ich habe:

conn = MySQLdb.connect(host, user, password, database)
cursor = conn.cursor()

try:
    cursor.execute(query)
    rows = cursor.fetchall()
    for row in rows:
        # do something with the data
except MySQLdb.Error, e:
    print "MySQL Error %d: %s" % (e.args[0], e.args[1])

Natürlich könnte ich es tun, indem ich einen weiteren Versuch in der Ausnahmeklausel mache, aber das ist unglaublich hässlich, und ich habe das Gefühl, dass es einen anständigen Weg geben muss, um dies zu erreichen.

Ben
quelle
2
Das ist ein guter Punkt. Ich würde wahrscheinlich ein paar Sekunden schlafen. Ich weiß nicht, was mit der MySQL-Installation auf dem Server falsch ist, aber es scheint, dass sie in einer Sekunde fehlschlägt und in der nächsten funktioniert.
Ben
3
@ Yuval A: Es ist eine häufige Aufgabe. Ich vermute, es ist sogar in Erlang eingebaut.
JFS
1
Um nur zu erwähnen, dass vielleicht nichts falsch ist, hat MySQL eine wait_timeout- Variable, um MySQL so zu konfigurieren, dass inaktive Verbindungen getrennt werden.
Andy

Antworten:

96

Wie wäre es mit:

conn = MySQLdb.connect(host, user, password, database)
cursor = conn.cursor()
attempts = 0

while attempts < 3:
    try:
        cursor.execute(query)
        rows = cursor.fetchall()
        for row in rows:
            # do something with the data
        break
    except MySQLdb.Error, e:
        attempts += 1
        print "MySQL Error %d: %s" % (e.args[0], e.args[1])
Dana
quelle
18
Oderfor attempt_number in range(3)
cdleary
8
Nun, ich mag meine irgendwie, weil es deutlich macht, dass die Versuche nur im Falle einer Ausnahme erhöht werden.
Dana
2
Ja, ich glaube, ich bin paranoider in Bezug auf whileEndlosschleifen als die meisten Menschen.
CDLeary
5
-1: Mag keine Pause. Wie "solange nicht fertig und versucht <3:" besser.
S.Lott
5
Ich mag die Pause, aber nicht die Weile. Dies ist eher C-ish als pythonisch. denn ich in Reichweite ist besser imho.
Hasen
77

Aufbauend auf Danas Antwort möchten Sie dies vielleicht als Dekorateur tun:

def retry(howmany):
    def tryIt(func):
        def f():
            attempts = 0
            while attempts < howmany:
                try:
                    return func()
                except:
                    attempts += 1
        return f
    return tryIt

Dann...

@retry(5)
def the_db_func():
    # [...]

Erweiterte Version, die das decoratorModul verwendet

import decorator, time

def retry(howmany, *exception_types, **kwargs):
    timeout = kwargs.get('timeout', 0.0) # seconds
    @decorator.decorator
    def tryIt(func, *fargs, **fkwargs):
        for _ in xrange(howmany):
            try: return func(*fargs, **fkwargs)
            except exception_types or Exception:
                if timeout is not None: time.sleep(timeout)
    return tryIt

Dann...

@retry(5, MySQLdb.Error, timeout=0.5)
def the_db_func():
    # [...]

So installieren Sie das decoratorModul :

$ easy_install decorator
dwc
quelle
2
Der Dekorateur sollte wahrscheinlich auch eine Ausnahmeklasse belegen, damit Sie keine nackte Ausnahme verwenden müssen. dh @retry (5, MySQLdb.Error)
cdleary
Raffiniert! Ich denke nie, Dekorateure zu verwenden: P
Dana
Das sollte "return func () im try-Block sein, nicht nur" func () ".
Robert Rossney
Bah! Danke für die Warnung.
dwc
Haben Sie tatsächlich versucht, dies auszuführen? Es funktioniert nicht. Das Problem ist, dass der Aufruf von func () in der tryIt-Funktion ausgeführt wird , sobald Sie die Funktion dekorieren , und nicht, wenn Sie die dekorierte Funktion tatsächlich aufrufen. Sie benötigen eine andere verschachtelte Funktion.
Steve Losh
12

UPDATE: gibt es eine besseren gehaltene Gabel der retrying Bibliothek namens Hartnäckigkeit , die mehr Funktionen unterstützt und ist in der Regel flexibler.


Ja, es gibt die Wiederholungsbibliothek mit einem Dekorator, der verschiedene Arten von Wiederholungslogik implementiert, die Sie kombinieren können:

Einige Beispiele:

@retry(stop_max_attempt_number=7)
def stop_after_7_attempts():
    print "Stopping after 7 attempts"

@retry(wait_fixed=2000)
def wait_2_s():
    print "Wait 2 second between retries"

@retry(wait_exponential_multiplier=1000, wait_exponential_max=10000)
def wait_exponential_1000():
    print "Wait 2^x * 1000 milliseconds between each retry,"
    print "up to 10 seconds, then 10 seconds afterwards"
Elias Dorneles
quelle
2
Die Wiederholungsbibliothek wurde durch die Hartnäckigkeitsbibliothek ersetzt .
Seth
8
conn = MySQLdb.connect(host, user, password, database)
cursor = conn.cursor()

for i in range(3):
    try:
        cursor.execute(query)
        rows = cursor.fetchall()
        for row in rows:
            # do something with the data
        break
    except MySQLdb.Error, e:
        print "MySQL Error %d: %s" % (e.args[0], e.args[1])
Webjunkie
quelle
1
Sie könnten unten ein anderes hinzufügen :else: raise TooManyRetriesCustomException
Bob Stein
6

Ich würde es so umgestalten:

def callee(cursor):
    cursor.execute(query)
    rows = cursor.fetchall()
    for row in rows:
        # do something with the data

def caller(attempt_count=3, wait_interval=20):
    """:param wait_interval: In seconds."""
    conn = MySQLdb.connect(host, user, password, database)
    cursor = conn.cursor()
    for attempt_number in range(attempt_count):
        try:
            callee(cursor)
        except MySQLdb.Error, e:
            logging.warn("MySQL Error %d: %s", e.args[0], e.args[1])
            time.sleep(wait_interval)
        else:
            break

Das Ausklammern der calleeFunktion scheint die Funktionalität aufzubrechen, so dass die Geschäftslogik leicht zu erkennen ist, ohne im Wiederholungscode hängen zu bleiben.

cdleary
quelle
-1: sonst und brechen ... eklig. Bevorzugen Sie ein klareres "wenn nicht fertig und zählen! = Try_count" als break.
S.Lott
1
"Ja wirklich?" Ich fand es auf diese Weise sinnvoller - wenn die Ausnahme nicht auftritt, brechen Sie aus der Schleife aus. Ich habe möglicherweise übermäßige Angst vor unendlichen while-Schleifen.
CDLeary
4
+1: Ich hasse Flaggenvariablen, wenn die Sprache die Codestrukturen enthält, um dies für Sie zu tun. Um Bonuspunkte zu erhalten, setzen Sie ein anderes auf das, um alle Versuche nicht zu bestehen.
Xorsyst
6

Wie S.Lott mag ich eine Flagge, um zu überprüfen, ob wir fertig sind:

conn = MySQLdb.connect(host, user, password, database)
cursor = conn.cursor()

success = False
attempts = 0

while attempts < 3 and not success:
    try:
        cursor.execute(query)
        rows = cursor.fetchall()
        for row in rows:
            # do something with the data
        success = True 
    except MySQLdb.Error, e:
        print "MySQL Error %d: %s" % (e.args[0], e.args[1])
        attempts += 1
Kiv
quelle
1
def successful_transaction(transaction):
    try:
        transaction()
        return True
    except SQL...:
        return False

succeeded = any(successful_transaction(transaction)
                for transaction in repeat(transaction, 3))
Peter Wood
quelle
1

1. Definition:

def try_three_times(express):
    att = 0
    while att < 3:
        try: return express()
        except: att += 1
    else: return u"FAILED"

2.Verwendung:

try_three_times(lambda: do_some_function_or_express())

Ich benutze es zum Parsen von HTML-Kontext.

user5637641
quelle
0

Dies ist meine generische Lösung:

class TryTimes(object):
    ''' A context-managed coroutine that returns True until a number of tries have been reached. '''

    def __init__(self, times):
        ''' times: Number of retries before failing. '''
        self.times = times
        self.count = 0

    def __next__(self):
        ''' A generator expression that counts up to times. '''
        while self.count < self.times:
            self.count += 1
        yield False

    def __call__(self, *args, **kwargs):
        ''' This allows "o() calls for "o = TryTimes(3)". '''
        return self.__next__().next()

    def __enter__(self):
        ''' Context manager entry, bound to t in "with TryTimes(3) as t" '''
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        ''' Context manager exit. '''
        return False # don't suppress exception

Dies ermöglicht Code wie den folgenden:

with TryTimes(3) as t:
    while t():
        print "Your code to try several times"

Auch möglich:

t = TryTimes(3)
while t():
    print "Your code to try several times"

Ich hoffe, dies kann verbessert werden, indem Ausnahmen intuitiver behandelt werden. Offen für Vorschläge.

user1970198
quelle
0

Sie können eine forSchleife mit einer elseKlausel verwenden, um maximale Wirkung zu erzielen:

conn = MySQLdb.connect(host, user, password, database)
cursor = conn.cursor()

for n in range(3):
    try:
        cursor.execute(query)
    except MySQLdb.Error, e:
        print "MySQL Error %d: %s" % (e.args[0], e.args[1])
    else:
        rows = cursor.fetchall()
        for row in rows:
            # do something with the data
        break
else:
    # All attempts failed, raise a real error or whatever

Der Schlüssel ist, aus der Schleife auszubrechen, sobald die Abfrage erfolgreich ist. Die elseKlausel wird nur ausgelöst, wenn die Schleife ohne a abgeschlossen ist break.

Verrückter Physiker
quelle