web-dev-qa-db-fra.com

existe-t-il un moyen pythonique d'essayer quelque chose jusqu'à un maximum de fois?

J'ai un script python qui interroge un serveur MySQL sur un hôte Linux partagé. Pour une raison quelconque, les requêtes à 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, en cas d'échec, d'essayer à nouveau, jusqu'à un nombre fixe d'essais. Je voudrais probablement essayer 5 fois avant d'abandonner complètement.

Voici le genre 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])

Clairement, je pourrais le faire en ayant 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.

72
Ben

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])
79
Dana

En vous appuyant sur la réponse de Dana, vous voudrez peut-être faire cela en tant que décoratrice:

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 module decorator

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

Ensuite...

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

Pour installer le module decorator :

$ easy_install decorator
74
dwc

UPDATE: il existe une fourche mieux maintenue de la bibliothèque de tentatives, appelée ténacité , qui prend en charge davantage de fonctionnalités et est en général plus flexible.


Oui, il y a la bibliothèque retrying library , qui a un décorateur qui implémente plusieurs types de logique de nouvelle tentative 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"
11
elias
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])
7
webjunkie

Comme S.Lott, j'aime bien 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
6
Kiv

Je refacturerais ça comme ça:

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 fonction callee semble rompre la fonctionnalité de sorte qu'il soit facile de voir la logique métier sans s'enliser dans le code de nouvelle tentative.

5
cdleary
def successful_transaction(transaction):
    try:
        transaction()
        return True
    except SQL...:
        return False

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

1.Définition:

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

2.Usage:

try_three_times(lambda: do_some_function_or_express())

Je l'utilise pour analyser le contexte html.

1
user5637641

Ceci est 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"

Également possible:

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

J'espère que cela peut être amélioré en gérant les exceptions de manière plus intuitive. Ouvert aux suggestions.

0
user1970198