¿Cómo volver a intentar después de la excepción?

252

Tengo un ciclo que comienza con for i in range(0, 100). Normalmente se ejecuta correctamente, pero a veces falla debido a las condiciones de la red. Actualmente lo tengo configurado para que en caso de falla, lo haga continueen la cláusula except (continúe con el siguiente número i).

¿Es posible para mí reasignar el mismo número iy ejecutar nuevamente la iteración fallida del bucle?

FurtiveFelon
fuente
1
Puede usar range(100)sin el primer parámetro. Si usa Python 2.x, incluso podría usarlo xrange(100), esto genera un iterador y usa menos memoria. (No es que importe solo con 100 objetos).
Georg Schölly
2
hay una solución muy elegante usando decoradores con soporte para manejar excepciones arbitrarias en ese hilo
zitroneneis

Respuestas:

380

Haga un while Truebucle for dentro, coloque su trycódigo dentro y salte de ese whilebucle solo cuando su código tenga éxito.

for i in range(0,100):
    while True:
        try:
            # do stuff
        except SomeSpecificException:
            continue
        break
zneak
fuente
30
@Ignacio, ¿ eh ? continuevuelve a intentar el whilebucle, por supuesto, no el for(!), por ilo que no es "el siguiente" nada, es exactamente lo mismo que en una pierna anterior (fallida) de la misma while, por supuesto.
Alex Martelli
13
Como señala xorsyst, es aconsejable establecer un límite de reintentos allí. De lo contrario, podría quedar atrapado en un bucle durante bastante tiempo.
Brad Koch
2
Este es un excelente ejemplo: medium.com/@echohack/…
Tony Melony
77
Definitivamente dejaría de lado el tiempo True: línea, de lo contrario, la ruptura continuará el ciclo externo hasta el agotamiento.
Jan
1
@Sankalp, me parece que esta respuesta es adecuada para el texto de la pregunta.
zneak
189

Prefiero limitar el número de reintentos, de modo que si hay un problema con ese elemento específico, eventualmente continuará con el siguiente, por lo tanto:

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
fuente
3
@ g33kz0r la construcción for-else en Python ejecuta la cláusula else si el bucle for no se rompe. Entonces, en este caso, esa sección se ejecuta si intentamos los 10 intentos y siempre obtenemos una excepción.
xorsyst
77
¡Esta es una respuesta genial! Realmente merece muchos más votos a favor. Utiliza perfectamente todas las instalaciones en Python, especialmente la else:cláusula menos conocida for.
pepoluan
2
¿No necesitas un descanso al final del intento: parte? Con la interrupción adicional en el intento: si el proceso se completa con éxito, el bucle se interrumpirá, si no se completa con éxito, irá directamente a la parte de excepción. ¿Tiene sentido? Si no pongo un descanso al final del intento: simplemente hace lo mismo 100 veces.
Tristán
1
@Tristan: la elsecláusula de tryesto hace que "si tiene éxito, se rompa" lo que está buscando.
PaulMcG
1
También prefiero un bucle for para volver a intentarlo. Una arruga en este código es que, si desea volver a aumentar la excepción cuando deja de intentarlo, necesita algo como "if intent = 9: raise" dentro de la exceptcláusula, y recuerde usar 9 y no 10.
PaulMcG
69

El paquete de reintentos es una buena manera de reintentar un bloque de código en caso de error.

Por ejemplo:

@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
fuente
44
Más generalmente, pypi tiene múltiples paquetes para decoradores de reintento: pypi.python.org/…
kert
¿hay alguna forma de imprimir el número del intento de reintento cada vez que falla?
dim_user
8
Como entendí que no se mantiene, la bifurcación más activa es github.com/jd/tenacity y quizás también se pueda usar github.com/litl/backoff .
Alexey Shrub
23

Aquí hay una solución similar a otras, pero generará la excepción si no tiene éxito en el número o los reintentos prescritos.

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
fuente
Buena respuesta, pero el nombre de la variable retrieses engañoso. Más bien debería serlo tries.
Lukas
Verdadero @Lukas. Fijo.
TheHerk
Muy buena solución gracias. Podría mejorarse agregando un retraso entre cada intento. Muy útil cuando se trata de API.
Sam
14

El enfoque más "funcional" sin usar esos feos bucles while:

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

tryAgain()
restbeckett
fuente
13
Lo siento, pero parece mucho más feo que las variantes "feo mientras bucles"; y soy aficionado a la programación funcional ...
lvella
99
Sin embargo, debe asegurarse de no recurrir profundamente: el tamaño de pila predeterminado en Python es 1000
Cal Paterson
55
Si esto va a ser 'funcional', la recursión debería ser:except: tryAgain(retries+1)
quamrana
El problema con esto es que necesitamos pasar el error como variables.
lowzhao
11

La forma más clara sería establecer explícitamente i. Por ejemplo:

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

    except MyException:
        continue
Tomi Kyöstilä
fuente
37
¿Es eso C o C ++? No puedo decirlo
Georg Schölly
55
@ Georgia Eso es Python, como se indica en la pregunta. ¿O dónde estás siendo sarcástico por alguna razón?
Jakob Borg
2
Esto no hace lo que pidió el OP. Podría hacerlo si pones i += 1justo después # do stuff.
fmalina
55
No pitónico. Debería usar rangepara este tipo de cosas.
Mystic
2
Estoy de acuerdo, esto definitivamente debería usar el rango.
user2662833
5

Una solución genérica con un tiempo de espera:

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()
Laurent LAPORTE
fuente
¿Es posible especificar una función separada para la verificación de errores? Tomaría el resultado de la devolución de llamada y pasaría a la función de verificación de errores para decidir si fue un fracaso o un éxito en lugar de usar un simpleexcept exception:
Pratik Khadloya
En lugar de un try … exceptpuede usar una ifdeclaración. Pero es menos pitónico.
Laurent LAPORTE
Esta solución no funciona. trinket.io/python/caeead4f6b La excepción lanzada por do_stuff no burbujea al generador. ¿Por qué lo haría, de todos modos? do_stuff se llama en el cuerpo del bucle for, que está en un nivel externo por sí solo, no anidado en el generador.
isarandi
Tiene derecho, pero por una razón diferente: la callbackfunción nunca se llama. He olvidado el paréntesis, reemplazar por 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

Mi versión es similar a varias de las anteriores, pero no usa un whilebucle separado y vuelve a generar la última excepción si fallan todos los reintentos. Podría establecerse explícitamente err = Noneen la parte superior, pero no es estrictamente necesario, ya que solo debería ejecutar el elsebloque final si hubo un error y, por errlo tanto, está configurado.

n8henrie
fuente
4

Usando while y un contador:

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
fuente
4

Usando recursividad

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
fuente
1
Condición de salida? ¿O esto funciona 100 * infinito?
ingyhere
3

Puede usar el paquete de reintentos de Python. Reintentando

Está escrito en Python para simplificar la tarea de agregar un comportamiento de reintento a casi cualquier cosa.

ManJan
fuente
2

Alternativas a retrying: tenacityybackoff (actualización 2020)

La biblioteca de reintentos era el camino a seguir anteriormente, pero lamentablemente tiene algunos errores y no ha recibido ninguna actualización desde 2016. Otras alternativas parecen ser retroceso y tenacidad . Durante el momento de escribir esto, la tenacidad tenía más estrellas GItHub (2.3k vs 1.2k) y se actualizó más recientemente, por lo tanto, elegí usarla. Aquí hay un ejemplo:

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

El código anterior genera algo como:

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

Más configuraciones para el tenacity.retryse enumeran en la página de tenacidad de GitHub .

np8
fuente
1

Si desea una solución sin bucles anidados e invocando breakel éxito, puede desarrollar un ajuste rápido retriablepara cualquier iterable. Aquí hay un ejemplo de un problema de red con el que me encuentro a menudo: la autenticación guardada caduca. El uso de este se leería así:

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
fuente
1

Yo uso siguiente en mis códigos,

   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
fuente
0

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

Voronin Roman
fuente
0

Aquí está mi opinión sobre este tema. La siguiente retryfunción admite las siguientes características:

  • Devuelve el valor de la función invocada cuando tiene éxito
  • Provoca la excepción de la función invocada si se agotan los intentos
  • Límite para el número de intentos (0 para ilimitado)
  • Espera (lineal o exponencial) entre intentos
  • Vuelva a intentarlo solo si la excepción es una instancia de un tipo de excepción específico.
  • Registro opcional de intentos
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)

Vea mi publicación para más información.

dux2
fuente
-2

Aquí está mi idea sobre cómo solucionar esto:

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)
Amina
fuente
77
Esto está fuera de lugar.
Chris Johnson el
-2

Recientemente trabajé con mi python en una solución a este problema y estoy feliz de compartirlo con los visitantes de stackoverflow. Por favor, den su opinión si es necesario.

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
fuente
-9

incremente su variable de bucle solo cuando la cláusula try tenga éxito

appusajeev
fuente