J'ai un script python qui interroge un serveur MySQL sur un hôte Linux partagé. Pour une raison quelconque, les requêtes adressées à MySQL renvoient souvent une erreur "Le serveur est parti":
_mysql_exceptions.OperationalError: (2006, 'MySQL server has gone away')
Si vous réessayez la requête immédiatement après, elle réussit généralement. Donc, j'aimerais savoir s'il existe un moyen raisonnable en python d'essayer d'exécuter une requête, et si cela échoue, de réessayer, jusqu'à un nombre fixe d'essais. Je voudrais probablement qu'il essaie 5 fois avant d'abandonner complètement.
Voici le type de code que j'ai:
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])
Il est clair que je pourrais le faire en faisant une autre tentative dans la clause d'exception, mais c'est incroyablement moche, et j'ai le sentiment qu'il doit y avoir un moyen décent d'y parvenir.
Réponses:
Que diriez-vous:
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])
la source
for attempt_number in range(3)
while
boucles infinies qui s'insinuent que la plupart des gens.En vous appuyant sur la réponse de Dana, vous voudrez peut-être faire ceci en tant que décorateur:
def retry(howmany): def tryIt(func): def f(): attempts = 0 while attempts < howmany: try: return func() except: attempts += 1 return f return tryIt
Ensuite...
@retry(5) def the_db_func(): # [...]
Version améliorée qui utilise le
decorator
moduleimport 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
Ensuite...
@retry(5, MySQLdb.Error, timeout=0.5) def the_db_func(): # [...]
Pour installer le
decorator
module :la source
MISE À JOUR: il existe un fork de la bibliothèque de relance mieux maintenu appelé ténacité , qui prend en charge plus de fonctionnalités et est en général plus flexible.
Oui, il existe la bibliothèque de relance , qui a un décorateur qui implémente plusieurs types de logique de relance que vous pouvez combiner:
Quelques exemples:
@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"
la source
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])
la source
else: raise TooManyRetriesCustomException
Je le refactoriserais comme ceci:
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
La factorisation de la
callee
fonction semble briser la fonctionnalité afin qu'il soit facile de voir la logique métier sans s'enliser dans le code de nouvelle tentative.la source
Comme S.Lott, j'aime un drapeau pour vérifier si nous avons terminé:
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
la source
def successful_transaction(transaction): try: transaction() return True except SQL...: return False succeeded = any(successful_transaction(transaction) for transaction in repeat(transaction, 3))
la source
1.Définition:
def try_three_times(express): att = 0 while att < 3: try: return express() except: att += 1 else: return u"FAILED"
2. utilisation:
try_three_times(lambda: do_some_function_or_express())
Je l'utilise pour analyser le contexte html.
la source
Voici ma solution générique:
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
Cela permet un code comme celui-ci:
with TryTimes(3) as t: while t(): print "Your code to try several times"
Aussi possible:
t = TryTimes(3) while t(): print "Your code to try several times"
Cela peut être amélioré en gérant les exceptions de manière plus intuitive, j'espère. Ouvert aux suggestions.
la source
Vous pouvez utiliser une
for
boucle avec uneelse
clause pour un effet maximal: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
La clé est de sortir de la boucle dès que la requête aboutit. La
else
clause ne sera déclenchée que si la boucle se termine sans unbreak
.la source