Come riprovare dopo un'eccezione?


252

Ho un ciclo che inizia con for i in range(0, 100). Normalmente funziona correttamente, ma a volte non riesce a causa delle condizioni della rete. Attualmente l'ho impostato in modo tale che, in caso di errore, lo farà continuenella clausola tranne ( passare al numero successivo per i).

È possibile per me riassegnare lo stesso numero ie ripetere nuovamente l'iterazione non riuscita del loop?


1
È possibile utilizzare range(100)senza il primo parametro. Se usi Python 2.x che potresti persino usare xrange(100), questo genera un iteratore e utilizza meno memoria. (Non che importi solo con 100 oggetti.)
Georg Schölly,


2
c'è una soluzione molto elegante che utilizza decoratori con supporto per la gestione delle eccezioni arbitrarie in quel thread
zitroneneis,

Risposte:


380

Esegui un while Trueciclo interno per, inserisci il trycodice all'interno e interrompi il whileciclo solo quando il codice ha esito positivo.

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

30
@Ignacio, eh ? continueriprova il whileciclo, ovviamente, non il for(!), quindi noni è "il prossimo" nulla - è esattamente lo stesso di una precedente (fallita) parte dello stesso , ovviamente. while
Alex Martelli,

13
Come nota xorsyst, è consigliabile fissare un limite di tentativi. Altrimenti potresti rimanere bloccato per un bel po 'di tempo.
Brad Koch,

2
Questo è un esempio eccellente: medium.com/@echohack/…
Tony Melony,

7
Vorrei sicuramente tralasciare il vero True: line, altrimenti la pausa continuerà il ciclo esterno fino all'esaurimento.
Jan

1
@Sankalp, mi sembra che questa risposta sia corretta per il testo della domanda.
zneak,

189

Preferisco limitare il numero di tentativi, in modo che se c'è un problema con quell'elemento specifico alla fine continuerai con quello successivo, quindi:

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 il costrutto for-else in Python esegue la clausola else se il ciclo for non si interrompe. Quindi, in questo caso, quella sezione viene eseguita se proviamo tutti e 10 i tentativi e otteniamo sempre un'eccezione.
xorsyst,

7
Questa è un'ottima risposta! Merita davvero molti più voti. Utilizza perfettamente tutte le strutture di Python, in particolare la else:clausola meno conosciuta di for.
pepoluan,

2
Non hai bisogno di una pausa alla fine del tentativo: parte? Con l'interruzione aggiuntiva in try :, se il processo si completa correttamente, il ciclo si interromperà, se non viene completato correttamente andrà direttamente alla parte di eccezione. Ha senso? Se non metto una pausa alla fine del tentativo: fa la cosa solo 100 volte.
Tristan,

1
@Tristan - la elseclausola del tryfa questo "in caso di successo, allora rompi" che stai cercando.
PaulMcG,

1
Preferisco anche un for-loop per riprovare. Una ruga in questo codice è che, se si desidera aumentare nuovamente l'eccezione quando si smette di provare, è necessario qualcosa di simile a "if try = 9: raise" all'interno della exceptclausola e ricordarsi di utilizzare 9 e non 10.
PaulMcG

69

Il pacchetto di tentativi è un buon modo per riprovare un blocco di codice in caso di errore.

Per esempio:

@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
Più in generale, ha PyPI pacchetti multipli per decoratori di tentativi: pypi.python.org/...
Kert

esiste comunque la possibilità di stampare il numero del tentativo di tentativo ogni volta che fallisce?
dim_user

8
Come ho capito non è mantenuto, fork più attivo è github.com/jd/tenacity e forse github.com/litl/backoff può anche essere usato.
Alexey Shrub

23

Ecco una soluzione simile ad altre, ma solleverà l'eccezione se non riesce nel numero prescritto o riprova.

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

Bella risposta, ma il nome della variabile retriesè fuorviante. Dovrebbe piuttosto essere tries.
Lukas,

TrueLukas. Fisso.
TheHerk,

Ottima soluzione grazie. Potrebbe essere migliorato aggiungendo un ritardo tra ogni tentativo. Molto utile quando si ha a che fare con le API.
Sam,

14

L'approccio più "funzionale" senza usare quei brutti cicli di ciclo:

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

tryAgain()

13
Mi dispiace, ma sembra molto più brutto delle varianti "brutti mentre passanti"; e sono appassionato di programmazione funzionale ...
lvella

9
Tuttavia, devi assicurarti di non ricorrere in profondità - la dimensione dello stack predefinita in Python è 1000
Cal Paterson,

5
Se questo sarà "funzionale", la ricorsione dovrebbe essere:except: tryAgain(retries+1)
quamrana,

Il problema è che dobbiamo passare l'errore come variabili.
Lowzhao,

11

Il modo più chiaro sarebbe quello di impostare esplicitamente i. Per esempio:

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

    except MyException:
        continue

37
Quello è C o C ++? Non so dirlo.
Georg Schölly,

5
@Georg That's Python, come indicato nella domanda. O dove sei sarcastico per qualche motivo?
Jakob Borg,

2
Ciò non fa ciò che l'OP ha richiesto. Potrebbe succedere se lo metti i += 1subito dopo # do stuff.
Fmalina,

5
Non pitonico. Dovrebbe usare rangeper questo tipo di cose.
Mystic

2
Sono d'accordo, questo dovrebbe assolutamente usare la gamma.
user2662833

5

Una soluzione generica con 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)

Uso:

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

È possibile specificare una funzione separata per il controllo degli errori? Richiederebbe l'output del callback e passerebbe alla funzione di controllo degli errori per decidere se si trattava di un errore o di un successo invece di utilizzare un sempliceexcept exception:
Pratik Khadloya,

Invece di a try … exceptpuoi usare una ifdichiarazione. Ma è meno pitonico.
Laurent LAPORTE,

Questa soluzione non funziona. trinket.io/python/caeead4f6b L'eccezione generata da do_stuff non genera bolle nel generatore. Perché dovrebbe, comunque? do_stuff è chiamato nel corpo del ciclo for, che è a un livello esterno da solo, non nidificato nel generatore.
Isarandi,

Il tuo diritto, ma per un motivo diverso: la callbackfunzione non viene mai chiamata. Ho dimenticato la parentesi, sostituirla con 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

La mia versione è simile a molte delle precedenti, ma non utilizza un whileloop separato e genera nuovamente l'ultima eccezione se tutti i tentativi falliscono. Potrebbe essere esplicitamente impostato err = Nonenella parte superiore, ma non strettamente necessario in quanto dovrebbe eseguire il elseblocco finale solo in caso di errore e quindi errimpostato.



4

Utilizzando while e un contatore:

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

Usando la ricorsione

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
Condizione di uscita? O questo funziona a 100 * infinito?
ingyhere

3

È possibile utilizzare il pacchetto di tentativi di Python. Nuovo tentativo

È scritto in Python per semplificare il compito di aggiungere un nuovo comportamento a qualsiasi cosa.


2

Alternative a retrying: tenacitye backoff(aggiornamento 2020)

La libreria di tentativi era in precedenza la strada da percorrere, ma purtroppo ha alcuni bug e non ha aggiornamenti dal 2016. Altre alternative sembrano essere backoff e tenacia . Durante la stesura di questo, la tenacia aveva più stelle GItHub (2.3k contro 1.2k) ed è stata aggiornata più di recente, quindi ho scelto di usarla. Ecco un esempio:

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

Il codice sopra emette qualcosa del tipo:

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.
.
.
.

Altre impostazioni per tenacity.retrysono elencate nella pagina GitHub di tenacia .


1

Se si desidera una soluzione senza loop nidificati e invocare breakil successo, è possibile sviluppare una soluzione rapida retriableper qualsiasi iterabile. Ecco un esempio di un problema di rete che incontro spesso: l'autenticazione salvata scade. L'uso di esso avrebbe letto in questo modo:

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

Uso il seguente nei miei codici,

   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

Ecco la mia opinione su questo problema. La seguente retryfunzione supporta le seguenti funzionalità:

  • Restituisce il valore della funzione invocata quando ha esito positivo
  • Solleva l'eccezione della funzione invocata se i tentativi sono esauriti
  • Limite per il numero di tentativi (0 per illimitato)
  • Attendere (lineare o esponenziale) tra i tentativi
  • Riprova solo se l'eccezione è un'istanza di un tipo di eccezione specifico.
  • Registrazione opzionale dei tentativi
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

Uso:

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)

Vedi il mio post per maggiori informazioni.


-2

Ecco la mia idea su come risolvere questo problema:

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
Questo è lontano dalla base.
Chris Johnson,

-2

di recente ho lavorato con il mio Python su una soluzione a questo problema e sono felice di condividerlo con i visitatori di StackOverflow, per favore fornisci un feedback se è necessario.

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

incrementare la variabile del ciclo solo quando la clausola try ha esito positivo

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.