Usar decoradores de Python para reintentar bloques de código

Olorunfemi Akinlua 21 junio 2023
  1. Importancia de los decoradores reintentar
  2. Use @retry para reintentar bloques de código en Python
  3. Use tenacidad para reintentar bloques de código en Python
Usar decoradores de Python para reintentar bloques de código

Podemos modificar una función o clase con un decorador para extender el comportamiento de la función sin cambiarla permanentemente. Este artículo explica cómo utilizar los decoradores de reintento para modificar una función existente sin realizar cambios en dicha función.

En este caso, la modificación vuelve a intentar la función varias veces en una situación dada donde su valor de retorno puede ser diferente al que queremos.

Importancia de los decoradores reintentar

Podemos usar decoradores para extender el comportamiento de una función específica, y podemos crear decoradores fácilmente para modificar esa función incluso cuando no tenemos acceso a ella o no queremos cambiarla.

Es posible que a menudo necesitemos esa función de una manera bastante específica, y ahí es donde entran los decoradores de Python. Entonces, creemos una función simple para mostrar cómo funcionan los decoradores.

La función simple, cociente(), toma dos argumentos y divide el primer argumento por el segundo argumento.

def quotient(a, b):
    return a / b


print(quotient(3, 7))

Producción :

0.42857142857142855

Sin embargo, si queremos que el resultado de la división sea siempre tal que el número mayor sea el que se divide (por lo que el resultado será 2.3333333333333335), podemos cambiar el código o utilizar decoradores.

Con decoradores, podemos extender el comportamiento de la función sin cambiar su bloque de código.

def improv(func):
    def inner(a, b):
        if a < b:
            a, b = b, a
        return func(a, b)

    return inner


@improv
def quotient(a, b):
    return a / b


print(quotient(3, 7))

Producción :

2.3333333333333335

La función improv() es la función decoradora que toma la función cociente() como argumento y contiene una función interna que toma el argumento de la función cociente() y aporta la funcionalidad adicional que necesita agregar.

Ahora, con los decoradores, podemos agregar una funcionalidad de reintento en una función en particular, especialmente con funciones a las que no tenemos acceso.

Los decoradores reintentar son útiles en escenarios en los que pueden existir errores o comportamientos impredecibles, y desea volver a intentar la misma operación cuando ocurran.

Un ejemplo típico es el manejo de una solicitud fallida dentro de un bucle for. En tales escenarios, podemos usar decoradores de reintento para administrar el reintento de esa solicitud específica una cantidad específica de veces.

Use @retry para reintentar bloques de código en Python

Para los decoradores de reintento, existen diferentes bibliotecas que proporcionan esta función, y una de esas bibliotecas es la biblioteca de reintento.

Con él, puede especificar las condiciones de esperar y detener desde Excepciones a los resultados de retorno esperados. Para instalar la librería reintentar, podemos usar el comando pip de la siguiente manera:

pip install retrying

Ahora, vamos a crear una función que genere aleatoriamente números entre 0 y 10, pero que genere un ValueError cuando tengamos una situación en la que el número sea mayor que 1.

import random


def generateRandomly():
    if random.randint(0, 10) > 1:
        raise ValueError("Number generated is greater than one")
    else:
        return "Finally Generated."


print(generateRandomly())

La salida del código tendrá el siguiente aspecto si el número generado en ese momento es mayor que uno.

Traceback (most recent call last):
  File "c:\Users\akinl\Documents\Python\SFTP\test.py", line 11, in <module>
    print(generateRandomly())
  File "c:\Users\akinl\Documents\Python\SFTP\test.py", line 6, in generateRandomly
    raise ValueError("Number generated is greater than one")
ValueError: Number generated is greater than one

No podemos tener una situación en la que se arroje un ValueError en nuestro código, por lo que podemos introducir un decorador reintentar para volver a intentar la función generateRandomly() hasta que no genere un ValueError.

import random
from retrying import retry


@retry
def generateRandomly():
    if random.randint(0, 10) > 1:
        raise ValueError("Number generated is greater than one")
    else:
        return "Finally Generated."


print(generateRandomly())

Producción :

Finally Generated.

Ahora, el decorador reintentar vuelve a intentar la operación aleatoria hasta que no tiene un ValueError, y solo tenemos la cadena Finally Generated..

Podemos ver la cantidad de veces que el código volvió a intentar “generar aleatoriamente ()” introduciendo una instrucción print dentro del bloque if.

import random
from retrying import retry


@retry
def generateRandomly():
    if random.randint(0, 10) > 1:
        print("1")
        raise ValueError("Number generated is greater than one")
    else:
        return "Finally Generated."


print(generateRandomly())

Producción :

1
1
1
1
1
1
1
Finally Generated.

Aquí, 8, pero podría ser diferente cuando ejecutas el código. Sin embargo, no podemos tener una situación en la que el código siga intentándolo durante mucho tiempo. Entonces, tenemos argumentos como stop_max_attempt_number y stop_max_delay.

import random
from retrying import retry


@retry(stop_max_attempt_number=5)
def generateRandomly():
    if random.randint(0, 10) > 1:
        print("1")
        raise ValueError("Number generated is greater than one")
    else:
        return "Finally Generated."


print(generateRandomly())

Producción :

1
1
1
Finally Generated.

La función se vuelve a intentar solo 5 veces, pero tiene éxito antes de la quinta vez, devuelve el valor Finalmente generado, o no lo hace y arroja el ValueError.

1
1
1
1
1
File "C:\Python310\lib\site-packages\retrying.py", line 247, in get
    six.reraise(self.value[0], self.value[1], self.value[2])
  File "C:\Python310\lib\site-packages\six.py", line 719, in reraise
    raise value
  File "C:\Python310\lib\site-packages\retrying.py", line 200, in call
    attempt = Attempt(fn(*args, **kwargs), attempt_number, False)
  File "c:\Users\akinl\Documents\Python\SFTP\test.py", line 9, in generateRandomly
    raise ValueError("Number generated is greater than one")
ValueError: Number generated is greater than one

Use tenacidad para reintentar bloques de código en Python

La biblioteca de reintentos puede ser un poco peculiar y ya no se mantiene, pero la biblioteca de tenacidad proporciona todas sus funciones con más herramientas a su disposición.

Para instalar tenacity, utilice el siguiente comando pip:

pip install tenacity

Podemos probar el mismo código con un intento de detener en 3.

import random
from tenacity import retry, stop_after_attempt


@retry(stop=stop_after_attempt(3))
def generateRandomly():
    if random.randint(0, 10) > 1:
        print("1")
        raise ValueError("Number generated is greater than one")
    else:
        return "Finally Generated."


print(generateRandomly())

La salida del código si el número aleatorio generado es menor que 1 dentro del marco de tiempo de tres intentos.

1
Finally Generated.

Sin embargo, si no es así, se lanzará la siguiente salida.

1
1
1
Traceback (most recent call last):
  File "C:\Python310\lib\site-packages\tenacity\__init__.py", line 407, in __call__
    result = fn(*args, **kwargs)
  File "c:\Users\akinl\Documents\Python\SFTP\test.py", line 9, in generateRandomly
    raise ValueError("Number generated is greater than one")
ValueError: Number generated is greater than one

The above exception was a direct cause of the following exception:

Traceback (most recent call last):
  File "c:\Users\akinl\Documents\Python\SFTP\test.py", line 14, in <module>
    print(generateRandomly())
  File "C:\Python310\lib\site-packages\tenacity\__init__.py", line 324, in wrapped_f
    return self(f, *args, **kw)
  File "C:\Python310\lib\site-packages\tenacity\__init__.py", line 404, in __call__
    do = self.iter(retry_state=retry_state)
  File "C:\Python310\lib\site-packages\tenacity\__init__.py", line 361, in iter
    raise retry_exc from fut.exception()
tenacity.RetryError: RetryError[<Future at 0x29a75442c20 state=finished raised ValueError>]
Olorunfemi Akinlua avatar Olorunfemi Akinlua avatar

Olorunfemi is a lover of technology and computers. In addition, I write technology and coding content for developers and hobbyists. When not working, I learn to design, among other things.

LinkedIn

Artículo relacionado - Python Decorator