2011-01-05 14 views
32

Tengo un código en Python que parece causar un error de forma probabilística porque está accediendo a un servidor y, en ocasiones, ese servidor tiene un error interno de 500 servidores. Quiero seguir intentándolo hasta que no obtenga el error. Mi solución fue:En Python intento hasta que no haya ningún error

while True: 
    try: 
     #code with possible error 
    except: 
     continue 
    else: 
     #the rest of the code 
     break 

Esto parece un truco para mí. ¿Hay una forma más pitonica de hacer esto?

+4

Err ... ¿Qué sucede cuando el servidor remoto muere? ¿Estará ahí consumiendo el 100% de un núcleo de CPU? – user9876

+0

continuar debería estar en else e irrumpir excepto ¿Es un error tipográfico? –

+2

@aand: No. Si se produce una excepción, que quiere intentarlo de nuevo (es decir: 'continue'), pero si no se produce ninguna excepción, que quiere que algunas cosas (esbozado por un comentario) y que salir de que el abuso de raro un bucle. ('Else' ejecuta si no se produce una excepción, que es la pieza que falta?) – delnan

Respuesta

33

No va a ser mucho más limpio. Esto no es algo muy limpio de hacer. En el mejor de los casos (que sería más legible de todos modos, ya que la condición para el break está allí con el while), podría crear una variable result = None y un ciclo mientras que is None. También debe ajustar las variables y puede reemplazar continue con la semántica tal vez correcta pass (no le importa si se produce un error, solo desea ignorarla) y soltar el break - esto también obtiene el resto del código, que solo se ejecuta una vez, fuera del ciclo. También tenga en cuenta que las cláusulas except: desnudas son malas por razones given in the documentation.

Ejemplo incorporar todo lo anterior:

result = None 
while result is None: 
    try: 
     # connect 
     result = get_data(...) 
    except: 
     pass 
# other code that uses result but is not involved in getting it 
+3

Si persiste una razón por la cual la conexión no está teniendo éxito, esta solución se reducirá en un ciclo infinito. –

+3

@BradKoch Por supuesto. Eso es inherente a la pregunta y, además, cualquier corrección (como un tiempo de espera total o un número limitado de intentos) es relativamente ortogonal a los cambios que describo. – delnan

+2

Pero cualquier respuesta propuesta debería ser segura, o al menos tener en cuenta las dificultades. Esto no ofrece protección contra el consumo de CPU al 100% y pone en peligro a los lectores futuros. –

15

Tal vez algo como esto:

connected = False 

while not connected: 
    try: 
     try_connect() 
     connected = True 
    except ...: 
     pass 
+3

Cualquier respuesta propuesta debe ser seguro, o por lo Por lo menos, tenga en cuenta las trampas. Esto no ofrece protección contra el consumo de CPU al 100% y pone en peligro a los lectores futuros. –

1

Tal basado decorador? Puede pasar como argumento de decorador la lista de excepciones sobre las cuales queremos volver a intentar y/o la cantidad de intentos.

def retry(exceptions=None, tries=None): 
    if exceptions: 
     exceptions = tuple(exceptions) 
    def wrapper(fun): 
     def retry_calls(*args, **kwargs): 
      if tries: 
       for _ in xrange(tries): 
        try: 
         fun(*args, **kwargs) 
        except exceptions: 
         pass 
        else: 
         break 
      else: 
       while True: 
        try: 
         fun(*args, **kwargs) 
        except exceptions: 
         pass 
        else: 
         break 
     return retry_calls 
    return wrapper 


from random import randint 

@retry([NameError, ValueError]) 
def foo(): 
    if randint(0, 1): 
     raise NameError('FAIL!') 
    print 'Success' 

@retry([ValueError], 2) 
def bar(): 
    if randint(0, 1): 
     raise ValueError('FAIL!') 
    print 'Success' 

@retry([ValueError], 2) 
def baz(): 
    while True: 
     raise ValueError('FAIL!') 

foo() 
bar() 
baz() 

, por supuesto, la parte 'tratar' debe ser trasladado a otra funcion becouse que usarlo en ambos bucles, pero es sólo ejemplo;)

+0

Poco de un comentario tarde, pero la duplicación del código podrían evitarse mediante el uso de "a _ en itertools.repeat (Ninguna, intentos veces =):" Si tries en su defecto, el bucle continúa para siempre, pero si es un número de intentos , termina después de eso muchas iteraciones. –

0

Aquí es un pequeño fragmento de código que utilizo para capturar el error como una cadena. Reintentaré hasta que tenga éxito. Esto capta todas las excepciones pero puede cambiar esto como lo desee.

start = 0 
str_error = "Not executed yet." 
while str_error: 
    try: 
     # replace line below with your logic , i.e. time out, max attempts 
     start = raw_input("enter a number, 0 for fail, last was {0}: ".format(start)) 
     new_val = 5/int(start) 
     str_error=None 
    except Exception as str_error: 
     pass 

ADVERTENCIA: Este código será atrapado en un bucle infinito hasta que se produce no es una excepción. Este es solo un ejemplo simple y PODRÍA REQUERIRLE que salga del ciclo antes o que duerma entre reintentos.

6

Aquí hay uno que falla duro después de 4 intentos, y espera 2 segundos entre intentos. Cambiar como desee para obtener lo que quiere formar éste:

from time import sleep 

for x in range(0, 4): # try 4 times 
    try: 
     # msg.send() 
     # put your logic here 
     str_error = None 
    except Exception as str_error: 
     pass 

    if str_error: 
     sleep(2) # wait for 2 seconds before trying to fetch the data again 
    else: 
     break 

Aquí se muestra un ejemplo con retardo de envío:

from time import sleep 

sleep_time = 2 
num_retries = 4 
for x in range(0, num_retries): 
    try: 
     # put your logic here 
     str_error = None 
    except Exception as str_error: 
     pass 

    if str_error: 
     sleep(sleep_time) # wait before trying to fetch the data again 
     sleep_time *= 2 # Implement your backoff algorithm here i.e. exponential backoff 
    else: 
     break 
+1

Me gusta esta respuesta mejor que otras porque esta es "buena" para otros procesos debido a la función de suspensión y también tiene intentos limitados. –

1

es una función de utilidad que escribí para envolver el reintento hasta que el éxito en un más ordenado Aquí paquete. Utiliza la misma estructura básica, pero evita la repetición. Se podría modificar para capturar y volver a lanzar la excepción en el último intento con relativa facilidad.

def try_until(func, max_tries, sleep_time): 
    for _ in range(0,max_tries): 
     try: 
      return func() 
     except: 
      sleep(sleep_time) 
    raise WellNamedException() 
    #could be 'return sensibleDefaultValue' 

puede entonces ser llamado como esto

result = try_until(my_function, 100, 1000) 

Si necesita pasar argumentos a my_function, se puede hacer ya sea esto por tener try_until formulando las alegaciones, o envolviéndolo en una lambda ningún argumento :

result = try_until(lambda : my_function(x,y,z), 100, 1000) 
1

Las recetas itertools.iter_except encapsula esta idea de "llamar a una función varias veces hasta que se produce una excepción". Es similar a la respuesta aceptada, pero la receta da un iterador en su lugar.

De las recetas:

def iter_except(func, exception, first=None): 
    """ Call a function repeatedly until an exception is raised.""" 
    try: 
     if first is not None: 
      yield first()   # For database APIs needing an initial cast to db.first() 
     while True: 
      yield func() 
    except exception: 
     pass 

que sin duda puede poner en práctica este último código directamente. Para mayor comodidad, utilizo una biblioteca separada, more_itertools, que implementa esta receta para nosotros (opcional).

Ejemplo:

import more_itertools as mit 

list(mit.iter_except([0, 1, 2].pop, IndexError)) 
# [2, 1, 0] 

Aquí el pop método (o función dada) se llama para cada iteración del objeto de lista hasta que un IndexError se eleva.

Para su caso, dada cierta connect_function y error esperado, usted puede hacer un iterador que llama a la función repetidamente hasta que se produce una excepción, por ejemplo:

mit.iter_except(connect_function, ConnectionError) 

En este punto, lo tratan como cualquier otro iterador al recorrerlo o llamar al next().

Cuestiones relacionadas