Comment réessayer après exception?


252

J'ai une boucle commençant par for i in range(0, 100). Normalement, il fonctionne correctement, mais parfois il échoue en raison des conditions du réseau. Actuellement, je l'ai configuré de manière à ce qu'en cas d'échec, il soit continuedans la clause except (continuez jusqu'au numéro suivant pour i).

Est-il possible pour moi de réaffecter le même numéro à iet d'exécuter à nouveau l'itération ayant échoué de la boucle?


1
Vous pouvez utiliser range(100)sans le premier paramètre. Si vous utilisez Python 2.x que vous pourriez même utiliser xrange(100), cela génère un itérateur et utilise moins de mémoire. (Pas que cela compte avec seulement 100 objets.)
Georg Schölly


2
il y a une solution très élégante en utilisant des décorateurs avec un support pour gérer les exceptions arbitraires dans ce fil
zitroneneis

Réponses:


380

Faites une à l' while Trueintérieur de votre boucle for, mettez votre trycode à l'intérieur et ne vous séparez de cette whileboucle que lorsque votre code réussit.

for i in range(0,100):
    while True:
        try:
            # do stuff
        except SomeSpecificException:
            continue
        break

30
@ Ignacio, hein ? continueréessaye la whileboucle, bien sûr, pas le for(!), donc ce in'est pas "le prochain" quoi que ce soit - c'est exactement la même chose que c'était sur une jambe précédente (échouée) de la même chose while, bien sûr.
Alex Martelli

13
Comme le note Xorsyst, il est conseillé de mettre une limite de nouvelle tentative là-bas. Sinon, vous pourriez rester bloqué en boucle pendant un certain temps.
Brad Koch

2
Ceci est un excellent exemple: medium.com/@echohack/…
Tony Melony

7
Je laisserais définitivement de côté la ligne True: Sinon, la rupture continuera la boucle externe jusqu'à l'épuisement.
Jan

1
@Sankalp, il me semble que cette réponse convient au texte de la question.
zneak

189

Je préfère limiter le nombre de tentatives, de sorte qu'en cas de problème avec cet élément spécifique, vous finirez par passer au suivant, ainsi:

for i in range(100):
  for attempt in range(10):
    try:
      # do thing
    except:
      # perhaps reconnect, etc.
    else:
      break
  else:
    # we failed all the attempts - deal with the consequences.

3
@ g33kz0r la construction for-else en Python exécute la clause else si la boucle for ne se casse pas. Donc, dans ce cas, cette section s'exécute si nous essayons les 10 tentatives et obtenons toujours une exception.
xorsyst

7
C'est une excellente réponse! Mérite vraiment beaucoup plus de votes positifs. Il utilise parfaitement toutes les fonctionnalités de Python, en particulier la else:clause la moins connue de for.
pepoluan

2
N'avez-vous pas besoin d'une pause à la fin de l'essai: partie? Avec la pause supplémentaire dans try :, si le processus se termine avec succès, la boucle se cassera, si elle ne se termine pas correctement, elle ira directement à la partie d'exception. Cela a-t-il du sens? Si je ne fais pas de pause à la fin de l'essai: ça fait tout simplement la chose 100 fois.
Tristan

1
@Tristan - la elseclause du tryfait ceci "en cas de succès, alors cassez" que vous recherchez.
PaulMcG

1
Je préfère également une boucle for pour réessayer. Une ride dans ce code est que, si vous voulez relancer l'exception lorsque vous abandonnez, vous avez besoin de quelque chose comme "if try = 9: raise" à l'intérieur de la exceptclause, et n'oubliez pas d'utiliser 9 et non 10.
PaulMcG

69

Le package de nouvelle tentative est un bon moyen de réessayer un bloc de code en cas d'échec.

Par exemple:

@retry(wait_random_min=1000, wait_random_max=2000)
def wait_random_1_to_2_s():
    print("Randomly wait 1 to 2 seconds between retries")

4
Plus généralement, pypi propose plusieurs packages pour les décorateurs de nouvelles tentatives: pypi.python.org/…
kert

existe-t-il de toute façon que vous pouvez imprimer le numéro de la nouvelle tentative à chaque échec?
dim_user

8
Si je comprends bien, ce n'est pas maintenu, la fourche la plus active est github.com/jd/tenacity et peut-être que github.com/litl/backoff peut également être utilisé.
Alexey Shrub

23

Voici une solution similaire aux autres, mais elle lèvera l'exception si elle ne réussit pas dans le nombre prescrit ou réessaye.

tries = 3
for i in range(tries):
    try:
        do_the_thing()
    except KeyError as e:
        if i < tries - 1: # i is zero indexed
            continue
        else:
            raise
    break

Bonne réponse, mais le nom de la variable retriesest trompeur. Cela devrait plutôt l'être tries.
Lukas

Vrai @Lukas. Fixé.
TheHerk

Très bonne solution merci. Il pourrait être amélioré en ajoutant un délai entre chaque essai. Très utile lorsque vous traitez avec des API.
Sam

14

L'approche la plus "fonctionnelle" sans utiliser les boucles while laides:

def tryAgain(retries=0):
    if retries > 10: return
    try:
        # Do stuff
    except:
        retries+=1
        tryAgain(retries)

tryAgain()

13
Je suis désolé, mais cela semble beaucoup plus moche que les variantes "moche en boucle"; et j'aime la programmation fonctionnelle ...
lvella

9
Vous devez cependant vous assurer de ne pas récurer profondément - la taille de pile par défaut en Python est de 1000
Cal Paterson

5
Si cela doit être «fonctionnel», la récursivité devrait être:except: tryAgain(retries+1)
quamrana

Le problème avec cela est que nous devons transmettre l'erreur en tant que variable.
lowzhao

11

Le moyen le plus clair serait de définir explicitement i. Par exemple:

i = 0
while i < 100:
    i += 1
    try:
        # do stuff

    except MyException:
        continue

37
Est-ce C ou C ++? Je ne sais pas.
Georg Schölly

5
@Georg C'est Python, comme indiqué dans la question. Ou où vous êtes sarcastique pour une raison quelconque?
Jakob Borg

2
Cela ne fait pas ce que le PO a demandé. Cela pourrait si vous mettez i += 1juste après # do stuff.
fmalina

5
Pas pythonique. Devrait utiliser rangepour ce genre de choses.
Mystic

2
Je suis d'accord, cela devrait certainement utiliser la plage.
user2662833

5

Une solution générique avec un timeout:

import time

def onerror_retry(exception, callback, timeout=2, timedelta=.1):
    end_time = time.time() + timeout
    while True:
        try:
            yield callback()
            break
        except exception:
            if time.time() > end_time:
                raise
            elif timedelta > 0:
                time.sleep(timedelta)

Usage:

for retry in onerror_retry(SomeSpecificException, do_stuff):
    retry()

Est-il possible de spécifier une fonction distincte pour la vérification des erreurs? Il faudrait la sortie du rappel et passer à la fonction de vérification des erreurs pour décider s'il s'agissait d'un échec ou d'un succès au lieu d'utiliser un simpleexcept exception:
Pratik Khadloya

Au lieu d'un, try … exceptvous pouvez utiliser une ifinstruction. Mais c'est moins pythonique.
Laurent LAPORTE

Cette solution ne fonctionne pas. trinket.io/python/caeead4f6b L'exception levée par do_stuff ne se propage pas au générateur. Pourquoi le ferait-il de toute façon? do_stuff est appelé dans le corps de la boucle for, qui est à un niveau externe à part entière, non imbriqué dans le générateur.
isarandi

Vous avez raison, mais pour une autre raison: la callbackfonction n'est jamais appelée. J'ai oublié la parenthèse, remplacez par callback().
Laurent LAPORTE

5
for _ in range(5):
    try:
        # replace this with something that may fail
        raise ValueError("foo")

    # replace Exception with a more specific exception
    except Exception as e:
        err = e
        continue

    # no exception, continue remainder of code
    else:
        break

# did not break the for loop, therefore all attempts
# raised an exception
else:
    raise err

Ma version est similaire à plusieurs des précédentes, mais n'utilise pas de whileboucle distincte et relance la dernière exception si toutes les tentatives échouent. Pourrait être explicitement défini err = Noneen haut, mais pas strictement nécessaire car il ne devrait exécuter le elsebloc final qu'en cas d'erreur et errest donc défini.



4

Utilisation de while et d'un compteur:

count = 1
while count <= 3:  # try 3 times
    try:
        # do_the_logic()
        break
    except SomeSpecificException as e:
        # If trying 3rd time and still error?? 
        # Just throw the error- we don't have anything to hide :)
        if count == 3:
            raise
        count += 1

4

Utiliser la récursivité

for i in range(100):
    def do():
        try:
            ## Network related scripts
        except SpecificException as ex:
            do()
    do() ## invoke do() whenever required inside this loop

1
Condition de sortie? Ou cela fonctionne-t-il à 100 * à l'infini?
ingyhere

3

Vous pouvez utiliser le package de nouvelle tentative Python. Nouvelle tentative

Il est écrit en Python pour simplifier la tâche d'ajouter un comportement de nouvelle tentative à peu près n'importe quoi.


2

Alternatives à retrying: tenacityet backoff(mise à jour 2020)

La bibliothèque de nouvelles tentatives était auparavant la voie à suivre, mais malheureusement, elle a quelques bugs et n'a pas eu de mises à jour depuis 2016. D'autres alternatives semblent être le backoff et la ténacité . Au moment d'écrire ces lignes, la ténacité avait plus d'étoiles GItHub (2,3k contre 1,2k) et a été mise à jour plus récemment, c'est pourquoi j'ai choisi de l'utiliser. Voici un exemple:

from functools import partial
import random # producing random errors for this example

from tenacity import retry, stop_after_delay, wait_fixed, retry_if_exception_type

# Custom error type for this example
class CommunicationError(Exception):
    pass

# Define shorthand decorator for the used settings.
retry_on_communication_error = partial(
    retry,
    stop=stop_after_delay(10),  # max. 10 seconds wait.
    wait=wait_fixed(0.4),  # wait 400ms 
    retry=retry_if_exception_type(CommunicationError),
)()


@retry_on_communication_error
def do_something_unreliable(i):
    if random.randint(1, 5) == 3:
        print('Run#', i, 'Error occured. Retrying.')
        raise CommunicationError()

Le code ci-dessus génère quelque chose comme:

Run# 3 Error occured. Retrying.
Run# 5 Error occured. Retrying.
Run# 6 Error occured. Retrying.
Run# 6 Error occured. Retrying.
Run# 10 Error occured. Retrying.
.
.
.

Plus de paramètres pour le tenacity.retrysont répertoriés sur la page GitHub de ténacité .


1

Si vous voulez une solution sans boucles imbriquées et invoquant breakle succès, vous pouvez développer un habillage rapide retriablepour tout itérable. Voici un exemple d'un problème de réseau que je rencontre souvent - l'authentification enregistrée expire. Son utilisation se lirait comme suit:

client = get_client()
smart_loop = retriable(list_of_values):

for value in smart_loop:
    try:
        client.do_something_with(value)
    except ClientAuthExpired:
        client = get_client()
        smart_loop.retry()
        continue
    except NetworkTimeout:
        smart_loop.retry()
        continue

1

J'utilise le suivant dans mes codes,

   for i in range(0, 10):
    try:
        #things I need to do
    except ValueError:
        print("Try #{} failed with ValueError: Sleeping for 2 secs before next try:".format(i))
        time.sleep(2)
        continue
    break

0

attempts = 3
while attempts:
  try:
     ...
     ...
     <status ok>
     break
  except:
    attempts -=1
else: # executed only break was not  raised
   <status failed>


0

Voici mon point de vue sur cette question. La retryfonction suivante prend en charge les fonctionnalités suivantes:

  • Renvoie la valeur de la fonction invoquée lorsqu'elle réussit
  • Déclenche l'exception de la fonction invoquée si les tentatives sont épuisées
  • Limite du nombre de tentatives (0 pour illimité)
  • Attente (linéaire ou exponentielle) entre les tentatives
  • Réessayez uniquement si l'exception est une instance d'un type d'exception spécifique.
  • Journalisation facultative des tentatives
import time

def retry(func, ex_type=Exception, limit=0, wait_ms=100, wait_increase_ratio=2, logger=None):
    attempt = 1
    while True:
        try:
            return func()
        except Exception as ex:
            if not isinstance(ex, ex_type):
                raise ex
            if 0 < limit <= attempt:
                if logger:
                    logger.warning("no more attempts")
                raise ex

            if logger:
                logger.error("failed execution attempt #%d", attempt, exc_info=ex)

            attempt += 1
            if logger:
                logger.info("waiting %d ms before attempt #%d", wait_ms, attempt)
            time.sleep(wait_ms / 1000)
            wait_ms *= wait_increase_ratio

Usage:

def fail_randomly():
    y = random.randint(0, 10)
    if y < 10:
        y = 0
    return x / y


logger = logging.getLogger()
logger.setLevel(logging.INFO)
logger.addHandler(logging.StreamHandler(stream=sys.stdout))

logger.info("starting")
result = retry.retry(fail_randomly, ex_type=ZeroDivisionError, limit=20, logger=logger)
logger.info("result is: %s", result)

Voir mon article pour plus d'informations.


-2

Voici mon idée sur la façon de résoudre ce problème:

j = 19
def calc(y):
    global j
    try:
        j = j + 8 - y
        x = int(y/j)   # this will eventually raise DIV/0 when j=0
        print("i = ", str(y), " j = ", str(j), " x = ", str(x))
    except:
        j = j + 1   # when the exception happens, increment "j" and retry
        calc(y)
for i in range(50):
    calc(i)

7
C'est loin de la base.
Chris Johnson

-2

J'ai récemment travaillé avec mon python sur une solution à ce problème et je suis heureux de la partager avec les visiteurs de stackoverflow, veuillez donner vos commentaires si cela est nécessaire.

print("\nmonthly salary per day and year converter".title())
print('==' * 25)


def income_counter(day, salary, month):
    global result2, result, is_ready, result3
    result = salary / month
    result2 = result * day
    result3 = salary * 12
    is_ready = True
    return result, result2, result3, is_ready


i = 0
for i in range(5):
    try:
        month = int(input("\ntotal days of the current month: "))
        salary = int(input("total salary per month: "))
        day = int(input("Total Days to calculate> "))
        income_counter(day=day, salary=salary, month=month)
        if is_ready:
            print(f'Your Salary per one day is: {round(result)}')
            print(f'your income in {day} days will be: {round(result2)}')
            print(f'your total income in one year will be: {round(result3)}')
            break
        else:
            continue
    except ZeroDivisionError:
        is_ready = False
        i += 1
        print("a month does'nt have 0 days, please try again")
        print(f'total chances left: {5 - i}')
    except ValueError:
        is_ready = False
        i += 1
        print("Invalid value, please type a number")
        print(f'total chances left: {5 - i}')

-9

incrémentez votre variable de boucle uniquement lorsque la clause try réussit

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.