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.