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?
Ç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
Peut-être quelque chose comme ça:
connected = False
while not connected:
try:
try_connect()
connected = True
except ...:
pass
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
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()
.
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)
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;)
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.
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.
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.