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?

FurtiveFelon
la source
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
zneak
la source
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.
xorsyst
la source
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")
goneri
la source
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
TheHerk
la source
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()
restbeckett
la source
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
Tomi Kyöstilä
la source
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()
Laurent LAPORTE
la source
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.

n8henrie
la source
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
Ranju R
la source
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
Joseph Thomas
la source
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.

ManJan
la source
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é .

np8
la source
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
Mikhail
la source
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
HS Rathore
la source
0

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

Voronin Roman
la source
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.

dux2
la source
-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)
Amine
la source
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}')
Rashad Kabir
la source
-9

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

appusajeev
la source