Manera pythonica de reintentar ejecutando una función

Cómo un Professinal de Python reintentaría ejecutando una función que solicitará un servicio web (el servicio web a veces falla)

La función:

def some_request(uri): try: req = requests.post('http://someuri.com', {'test': 'yes'}) except Exception as e: return False return {'id': req.json()['value'], 'other': req.json()['other']} 

¿Manejas el rebash con un rato u otro idioma de Python?

Dame una pista sobre cómo hacerlo de la manera correcta.

Definir la utilidad de rebash:

 # Retry utility # set logging for `retry` channel import logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger('retry') # Define Exception class for retry class RetryException(Exception): u_str = "Exception ({}) raised after {} tries." def __init__(self, exp, max_retry): self.exp = exp self.max_retry = max_retry def __unicode__(self): return self.u_str.format(self.exp, self.max_retry) def __str__(self): return self.__unicode__() # Define retry util function def retry_func(func, max_retry=10): """ @param func: The function that needs to be retry @param max_retry: Maximum retry of `func` function, default is `10` @return: func @raise: RetryException if retries exceeded than max_retry """ for retry in range(1, max_retry + 1): try: return func() except Exception, e: logger.info('Failed to call {}, in retry({}/{})'.format(func.func, retry, max_retry)) else: raise RetryException(e, max_retry) 

Úsalo:

 from functools import partial import requests def some_request(uri, post): req = requests.post(uri, post) return req uri = 'http://someuri.com' post = {'test': 'yes'} try: retry_func(partial(some_request, uri, post), max_retry=3) except RetryException, e: print(e) 

Salida:

 INFO:requests.packages.urllib3.connectionpool:Starting new HTTP connection (1): someuri.com INFO:retry:Failed to call , in retry(1/3) INFO:requests.packages.urllib3.connectionpool:Starting new HTTP connection (1): someuri.com INFO:retry:Failed to call , in retry(2/3) INFO:requests.packages.urllib3.connectionpool:Starting new HTTP connection (1): someuri.com INFO:retry:Failed to call , in retry(3/3) Exception (HTTPConnectionPool(host='someuri.com', port=80): Max retries exceeded with url: / (Caused by : [Errno -2] Name or service not known)) raised after 3 tries. 

El patrón que utilizo reintenta un número limitado de veces, duerme durante un breve intervalo de tiempo que aumenta exponencialmente entre cada bash y, finalmente, aumenta la última excepción vista después de un fallo repetido:

 def wrappedRequest( self, uri, args ): lastException = None interval = 1.0 for _ in range(3): try: result = requests.post(uri, args) return result except Exception as e: lastException = e time.sleep(interval) interval *= 2.0 raise lastException 

(De hecho, compruebo específicamente las excepciones HTTPError, URLError, IOError y BadStatusLine en lugar de la amplia red de Exception).

Esto definitivamente requiere un decorador.

probablemente querrás algo como:

 import functools def retry(func): @functools.wraps(func) def wrapper(*args, **kwargs): retExc = None for i in xrange(4): try: return func(*args, **kwargs) except Exception, e: retExc = e raise retExc return wrapper 

y luego para ejecutar su código, simplemente use:

 @retry def some_request(...): ... 

podría hacerlo más sofisticado agregando un poco de sueño, agregando un número de Tiempos para reintentar, etc., pero esto lo hará.

Aquí hay un decorador generalizado que te permite especificar el número de rebashs y también el tipo de excepción que se debe ignorar:

 from functools import wraps def retry(count=5, exc_type=Exception): def decorator(func): @wraps(func): def result(*args, **kwargs): for _ in range(count): try: return func(*args, **kwargs) except exc_type: pass raise return result return decorator 

Úsalo así:

 @retry(count=10, exc_type=IOError) def read_file(): pass # do something