En Python essayez jusqu'à ce qu'aucune erreur
J'ai un morceau de code en Python qui semble provoquer une erreur probabiliste car il accède à un serveur et parfois ce serveur a une erreur de serveur interne 500. Je veux continuer à essayer jusqu'à ce que je n'ai pas l'erreur. Ma solution était:
while True:
try:
#code with possible error
except:
continue
else:
#the rest of the code
break
Cela me semble être un hack. Y a-t-il un moyen plus pythonique de le faire?
9 réponses
Il 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, puisque la condition pour le break
est là-haut avec le while
), Vous pouvez créer une variable result = None
et la boucle pendant qu'elle is None
. Vous devez également ajuster les variables et vous pouvez remplacer continue
par le pass
sémantiquement peut-être correct (vous ne vous souciez pas si une erreur se produit, vous voulez juste l'ignorer) et supprimer le break
- cela obtient également le reste du code, qui ne s'exécute qu'une fois, de la boucle. Notez également que les clauses bare except:
sont mauvaises pour des 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
Peut-être quelque chose comme ceci:
connected = False
while not connected:
try:
try_connect()
connected = True
except ...:
pass
En voici un qui échoue après 4 tentatives, et attend 2 secondes entre les tentatives. Changez comme vous souhaitez obtenir ce que vous voulez former 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 l'interruption:
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
Voici une fonction utilitaire que j'ai écrite pour envelopper la nouvelle tentative jusqu'au succès dans un paquet plus propre. Il utilise la même structure de base, mais empêche la répétition. Il pourrait être modifié pour attraper et repousser l'exception sur l'essai final 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 ceci
result = try_until(my_function, 100, 1000)
Si vous avez besoin de passer des arguments à my_function
, vous pouvez soit le faire en envoyant try_until
les arguments, soit en les enveloppant dans un lambda sans argument:
result = try_until(lambda : my_function(x,y,z), 100, 1000)
Le itertools.iter_except
recettes encapsule cette idée de "l'appel d'une fonction à plusieurs reprises jusqu'à ce qu'une exception est levée". C'est similaire à la réponse acceptée, mais la recette donne un itérateur à la place.
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. Pour plus de commodité, j'utilise une bibliothèque séparée, more_itertools
, cela 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 fonction donnée) est appelée pour chaque itération de l'objet list jusqu'à ce qu'un IndexError
soit déclenché.
Pour votre cas, compte tenu de l'erreur connect_function
et attendue, vous pouvez créer un itérateur qui appelle la fonction à plusieurs reprises jusqu'à ce qu'une exception soit déclenchée, par exemple
mit.iter_except(connect_function, ConnectionError)
À ce stade, traitez-le comme n'importe quel autre itérateur en le bouclant ou en appelant next()
.
Peut-être décorateur basé? Vous pouvez passer comme arguments de décorateur liste des 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 'try' devrait être déplacée vers une autre fonction car nous l'utilisons dans les deux boucles mais c'est juste un exemple;)
Comme la plupart des autres, je recommande d'essayer un nombre fini de fois et de dormir entre les tentatives. De cette façon, vous ne vous trouvez pas dans une boucle infinie au cas où quelque chose arriverait réellement au serveur distant.
Je recommande également de continuer uniquement lorsque vous obtenez l'exception spécifique que vous attendez. De cette façon, vous pouvez toujours gérer les exceptions auxquelles vous ne vous attendez peut-être pas.
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 suite dans le bloc except, vous sautez le reste de la boucle jusqu'à ce que le bloc try fonctionne, la condition while est satisfaite ou une exception autre que HTTPError apparaît.
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 fonctionner. Il définit e sur "et la boucle while vérifie si elle est toujours". S'il y a une erreur détectée par l'instruction try, elle imprime que la connexion a été refusée, attend 1 seconde et recommence. Il continuera jusqu'à ce qu'il n'y ait pas d'erreur dans try, qui définit ensuite e sur ' ', ce qui tue la boucle while.
Voici un petit morceau de code que j'utilise pour capturer l'erreur sous forme de chaîne. Va réessayer jusqu'à ce qu'il réussisse. Cela attrape toutes les exceptions mais vous pouvez changer cela comme vous le souhaitez.
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 sera bloqué dans une boucle forever 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 les tentatives.