web-dev-qa-db-fra.com

En Python, essayez jusqu'à ce que plus aucune erreur

J'ai un morceau de code dans Python qui semble provoquer une erreur probablement parce qu'il accède à un serveur et que ce serveur a parfois une erreur de 500 serveur interne. Je veux continuer d'essayer jusqu'à ce que je ne reçois pas l'erreur. Ma solution était:

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

Cela ressemble à un bidouillage pour moi. Existe-t-il un moyen plus pythonique de le faire?

43
murgatroid99

Ça ne sera pas beaucoup plus propre. Ce n'est pas une chose très propre à faire. Au mieux (ce qui serait de toute façon plus lisible, étant donné que la condition pour la variable break est identique à celle de la variable while), vous pouvez créer une variable result = None et une boucle tandis qu'elle is None. Vous devez également ajuster les variables et vous pouvez remplacer continue par le sémantique peut-être correct pass (vous ne vous inquiétez pas si une erreur se produit, vous voulez juste l’ignorer) et lâchez la break - ceci récupère également le reste du code, qui ne s'exécute qu'une fois, en dehors de la boucle. Notez également que les clauses except: nues sont néfastes pour les raisons données dans la documentation .

Exemple incorporant tout ce qui précède:

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
46
user395760

Peut-être quelque chose comme ça:

connected = False

while not connected:
    try:
        try_connect()
        connected = True
    except ...:
        pass
19
mouad

En voici une qui échoue après 4 tentatives et attend 2 secondes entre les tentatives. Changez comme vous voulez pour obtenir ce que vous voulez de celui-ci:

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

Voici un exemple avec 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
14
radtek

Le itertools.iter_except recipes encapsule cette idée "en appelant une fonction de manière répétée jusqu'à ce qu'une exception soit déclenchée". C'est similaire à la réponse acceptée, mais la recette donne un itérateur à la place.

À partir des recettes:

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

Vous pouvez certainement implémenter ce dernier code directement. Par commodité, j’utilise une bibliothèque séparée, more_itertools , qui implémente cette recette pour nous (facultatif). 

Code

import more_itertools as mit

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

Détails

Ici, la méthode pop (ou une fonction donnée) est appelée pour chaque itération de l'objet liste jusqu'à ce qu'une IndexError soit déclenchée.

Dans votre cas, compte tenu de connect_function et de l'erreur attendue, vous pouvez créer un itérateur qui appelle la fonction de manière répétée jusqu'à ce qu'une exception soit déclenchée, par exemple.

mit.iter_except(connect_function, ConnectionError)

À ce stade, traitez-le comme tout autre itérateur en le parcourant ou en appelant next().

2
pylang

Voici une fonction d’utilité que j’ai écrite pour intégrer les tentatives d’essai jusqu’à la réussite dans un paquet plus ordonné. Il utilise la même structure de base, mais empêche la répétition. Il pourrait être modifié pour capturer et renvoyer l'exception lors du dernier essai relativement facilement.

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'

Peut alors être appelé comme ça

result = try_until(my_function, 100, 1000)

Si vous devez passer des arguments à my_function, vous pouvez le faire en laissant try_until transmettre les arguments, ou en l'enveloppant dans un no argument lambda:

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

Peut-être que décorateur?? Vous pouvez passer en tant que décorateur une liste d’exceptions sur lesquelles nous voulons réessayer et/ou nombre de tentatives.

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

bien sûr, la partie "essayer" devrait être déplacée dans une autre fonction, car nous l'utilisons dans les deux boucles, mais ce n'est qu'un exemple;)

1
virhilo
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)

Cela devrait marcher. Il définit e sur '' et la boucle while vérifie si elle est toujours ''. Si une erreur est interceptée dans l'instruction try, elle indique que la connexion a été refusée, attend 1 seconde, puis recommence. Il continuera jusqu'à ce qu'il n'y ait plus d'erreur dans try, qui définit ensuite e avec '', ce qui tue la boucle while. 

1
user9311010

Comme la plupart des autres, je vous conseillerais d'essayer un nombre fini de fois et de dormir entre chaque tentative. De cette façon, vous ne vous retrouvez pas dans une boucle infinie au cas où quelque chose arriverait réellement au serveur distant.

Je vous recommande également de ne continuer que lorsque vous obtenez l'exception spécifique que vous attendez. De cette façon, vous pouvez toujours gérer des exceptions inattendues.

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

En outre, vous n'avez pas besoin d'un bloc else. En raison de la continuation dans le bloc d'exception, vous ignorez le reste de la boucle jusqu'à ce que le bloc try fonctionne, que la condition while soit satisfaite ou qu'une exception autre que HTTPError se présente.

1
noname

Voici un court morceau de code que j'utilise pour capturer l'erreur sous forme de chaîne. Je vais réessayer jusqu'à ce qu'il réussisse. Cela intercepte toutes les exceptions mais vous pouvez le changer à votre guise.

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

AVERTISSEMENT: Ce code restera bloqué dans une boucle permanente jusqu'à ce qu'aucune exception ne se produise. Ceci est juste un exemple simple et PEUT vous obliger à sortir de la boucle plus tôt ou à dormir entre deux tentatives.

0
radtek