Comprender y utilizar los Python decorators

Los Python decorators o decorador de Python son una forma de extender la funcionalidad básica de una función sin cambiar su código fuente subyacente.

¿Qué son los decoradores y para qué sirven?

El uso de Python decorators no se explica en detalle en muchos tutoriales de Python. La razón es que para entender los decoradores es necesario primero estar bien familiarizado con las propias funciones. Los decoradores de Python pueden reconocerse en el código por su operador de Python, esto es, el signo “@” seguido del nombre del decorador de la función.

Nota

Puedes aprender más sobre construcciones avanzadas de programación en Python en los artículos:

Puedes ver las sintaxis básicas de una llamada en un Python decorator en el siguiente ejemplo de código, que, sin embargo, no implementa ninguna funcionalidad:

@decorador
def función():
 pass
Python

El código almacenado en “decorador” se ejecutaría en este ejemplo cuando se llame a la función llamada “función”.

Los decoradores se utilizan a menudo en la programación orientada a objetos con Python. Por ejemplo, existe el decorador Python property. Se utiliza como equivalente a los métodos getter y setter en otros lenguajes de programación.

Consejo

Python es un lenguaje de programación ideal para proyectos web, gracias, por ejemplo, a construcciones prácticas como los decoradores. También es excelente para proyectos web IONOS Deploy Now. Con esta herramienta puedes desarrollar y construir fácilmente tu proyecto a través de GitHub y hacer un seguimiento.

Uso de los decoradores de Python

Ampliación de las funciones básicas mediante decoradores de Python

Los decoradores de Python se utilizan en la mayoría de los casos para ampliar la funcionalidad básica de una función. Esto es práctico si utilizas la misma función básica en varios casos de uso y quieres extenderla individualmente. Un ejemplo de código sencillo, pero ya ejecutable, ilustra el de los decoradores para el fin descrito:

def dec(función):
 def foo(x):
  print("Antes de llamar a la función " + función.__nombre__)
  función(x)
       print("Después de llamar a la función " + función.__nombre__)
 return foo

@dec
def bar(y):
 print("Llamada a la función bar con el valor " + str(y))

bar("Test")
Python

En el código de ejemplo anterior, primero se crea el decorador “dec”, que a su vez contiene una función llamada “foo”. Como puedes ver, este decorador básicamente no es más que una función wrapper independiente, que en este caso contiene otra función llamada “foo”. “Foo2” muestra primero que te encuentras ante la llamada de la función pasada al decorador, después se ejecuta la función con el parámetro y, finalmente, se ejecuta una llamada más de python-print, que indica que estás después de la llamada de la función pasada al decorador.

La segunda parte del código consiste en la definición de una función llamada “bar”, que toma un parámetro de paso llamado “y”. La funcionalidad de bar es fácil de entender: muestra en pantalla la frase “Llamada a la función de bar con el valor y”, utilizando para y el valor pasado como parámetro. Lo especial de la función bar es que ha sido decorada. Esto se puede ver en el ejemplo de código en la línea “@dec” antes de la definición de la función.

Pero ¿qué significa exactamente que la función ha sido decorada? Supongamos que se hubiera omitido el Python decorator, es decir, la línea de código “@dec”. La llamada a la función “bar”, con la que concluye el ejemplo de código, proporcionaría entonces el siguiente resultado:

Llamada a la función bar con el valor Test
Python

Lo que ocurre aquí es exactamente lo que se espera de la llamada a la función: la cadena Python “Test” pasada como parámetro se introduce en la sentencia “print”. La función devuelve el resultado visto en el ejemplo anterior.

Ahora observa el resultado de la misma llamada a “bar”, esta vez decorando la función “bar” con el Python decorator:

Antes de llamar a la función bar
Llamada a la función bar con el valor Test
Después de llamar a la función bar

Lo que ves tal vez te sorprenda: después de que la función haya sido decorada, no solo muestra el resultado de su propia sentencia “print” en la pantalla. El resultado de la función decoradora se ha encapsulado de forma que ahora también se incluyen las dos sentencias “print” de la función auxiliar “foo”. La funcionalidad principal de “bar” se ha ampliado añadiendo dos sentencias “print” adicionales mediante el uso del Python decorator.

Por supuesto, este ejemplo es artificial y no sigue ninguna lógica de programación. Sin embargo, es suficiente para ofrecer una visión general del funcionamiento de los Python decorators. Por supuesto, puedes incluir cualquier funcionalidad de Python en la función del decorador.

Consulta de condiciones recurrentes con Python decorators

Es posible que desees ejecutar ciertas funciones únicamente cuando se cumpla una condición determinada. Para ello, probablemente ya estés familiarizado con las funciones if else de Python. Sin embargo, si estas condiciones deben comprobarse en diferentes situaciones, puede ser útil para la claridad de tu código encapsular la condición en un Python decorator.

De nuevo, un ejemplo de código ayuda a visualizar el uso del decorador. Algunos operadores matemáticos solo están definidos para los números naturales. Por lo tanto, sería útil tener una función decoradora que compruebe si el parámetro de paso de una función es un número natural:

def número_natural(función):
 def test(x):
  if type(x) == int and x > 0:
   return función(x)
  else:
   raise Exception("El argumento no es un número natural")

@número_natural
def fac(n):
 if n == 1:
  return 1
 else:
   return n * fac(n-1)

print(fac(5))
print(fac(-1))
Python

En el código anterior, primero has de definir el Python decorator, llamado “número_natural”, que comprueba si el argumento de la función que se le pasa llamado “función” es un número natural. Para ello, primero se comprueba el tipo del argumento en la condición if. También se comprueba si el argumento es un número positivo mayor que 0. Si es así, se ejecuta la función pasada al decorador como parámetro. En caso contrario, se lanza una excepción, informando de que el argumento de la función no es un número natural.

La forma en que funciona el Python decorator puede ilustrarse en la práctica observando la función llamada “fac” que está decorada con dicho decorador. Se define en el código y se llama primero con el valor “5” y luego con el valor “-1”. El resultado es el siguiente:

120
Traceback (most recent call last):
  File "<pyshell#17>", line 1, in <module>
    fac(-1)
  File "<pyshell#11>", line 6, in test
    raise Exception("El argumento no es un número natural ")
Exception: El argumento no es un número natural

Primero tienes el número “120”, que corresponde al factorial de 5. Así que, para números naturales, la función “fac” funciona. Sin embargo, llamar a la función “fac” con un número negativo conduce a un error y eso se debe al Python decorator. Dado que un número negativo no es un número natural, la función factorial “fac” no debe ejecutarse.