Geekflare recibe el apoyo de nuestra audiencia. Podemos ganar comisiones de afiliación de los enlaces de compra en este sitio.
En Desarrollo Última actualización: 24 de septiembre de 2023
Compartir en:
Escáner de seguridad de aplicaciones web Invicti - la única solución que ofrece verificación automática de vulnerabilidades con Proof-Based Scanning™.

Los decoradores en Python son una construcción increíblemente útil en Python. Usando decoradores en Python, podemos modificar el comportamiento de una función envolviéndola dentro de otra función. Los decoradores nos permiten escribir código más limpio y compartir funcionalidad. Este artículo es un tutorial no sólo sobre cómo utilizar decoradores, sino también sobre cómo crearlos.

Conocimientos previos

El tema de los decoradores en Python requiere algunos conocimientos previos. A continuación, he enumerado algunos conceptos con los que ya debería estar familiarizado para que este tutorial tenga sentido. También he enlazado recursos donde puede repasar los conceptos si es necesario.

Pitón básico

Este tema es un tema más intermedio/avanzado. Como resultado, antes de intentar aprenderlo, ya debería estar familiarizado con los conceptos básicos de Python, como los tipos de datos, las funciones, los objetos y las clases.

También debería comprender algunos conceptos orientados a objetos, como los getters, setters y constructores. Si no está familiarizado con el lenguaje de programación Python, aquí tiene algunos recursos para empezar.

Las funciones son ciudadanos de primera clase

Además de los conocimientos básicos de Python, también debería conocer este concepto más avanzado de Python. Las funciones, y prácticamente todo lo demás en Python, son objetos como int o string. Debido a que son objetos, puede hacer algunas cosas con ellos, a saber:

  • Puede pasar una función como argumento a otra función del mismo modo que pasa una cadena o un int como argumento de una función.
  • Las funciones también pueden ser devueltas por otras funciones del mismo modo que devolvería otros valores string o int.
  • Las funciones pueden almacenarse en variables

De hecho, la única diferencia entre los objetos funcionales y otros objetos es que los objetos funcionales contienen el método mágico __call__().

Esperemos que, llegados a este punto, se sienta cómodo con los conocimientos previos. Podemos empezar a discutir el tema principal.

¿Qué es un decorador de Python?

Un decorador de Python es simplemente una función que toma una función como argumento y devuelve una versión modificada de la función que se le pasó. En otras palabras, la función foo es un decorador si toma como argumento la función bar y devuelve otra función baz.

La función baz es una modificación de bar en el sentido de que dentro del cuerpo de baz hay una llamada a la función bar. Sin embargo, antes y después de la llamada a bar, baz puede hacer cualquier cosa. Eso ha sido un trabalenguas; he aquí algo de código para ilustrar la situación:

# Foo es un decorador, toma otra función, bar como argumento
def foo(bar):

   # Aquí creamos baz, una versión modificada de bar
 # baz llamará a bar pero puede hacer cualquier cosa antes y después de la llamada a la función
 def baz():

       # Antes de llamar a bar, imprimimos algo
 print("Algo")

 # Luego ejecutamos bar haciendo una llamada a la función
 bar()

 # Luego imprimimos algo más después de ejecutar bar
 print("Algo más")

 # Por último, foo devuelve baz, una versión modificada de bar
 return baz

¿Cómo crear un decorador en Python?

Para ilustrar cómo se crean y utilizan los decoradores en Python, voy a ilustrarlo con un ejemplo sencillo. En este ejemplo, crearemos una función decoradora logger que registrará el nombre de la función que está decorando cada vez que esa función se ejecute.

Para empezar, creamos la función decoradora. El decorador recibe func como argumento. func es la función que estamos decorando.

def crear_logger(func):
   # El cuerpo de la función va aquí

Dentro de la función decoradora, vamos a crear nuestra función modificada que registrará el nombre de func antes de ejecutar func.

# Dentro de create_logger
def modified_func():
 print("Calling: ", func.__name__)
 func()

A continuación, la función create_logger devolverá la función modificada. Como resultado, toda nuestra función create_logger tendrá el siguiente aspecto:

def create_logger(func):
 def modified_func():
 print("Llamando: ", func.__name__)
 func()

 return modified_function

Hemos terminado de crear el decorador. La función create_logger es un ejemplo sencillo de función decoradora. Tome func, que es la función que estamos decorando, y devuelve otra función, modified_func. modified_func registra primero el nombre de func, antes de ejecutar func.

Cómo utilizar decoradores en Python

Para utilizar nuestro decorador, utilizamos la sintaxis @ de la siguiente manera:

@create_logger
def decir_hola():
 print("¡Hola, mundo!")

Ahora podemos llamar a say_hello() en nuestro script, y la salida debería ser el siguiente texto:

Llamada: say_hello
"Hola, mundo"
Captura de pantalla de un programa que utiliza decoradores python

Pero, ¿qué está haciendo el @create_logger? Pues está aplicando el decorador a nuestra función say_hello. Para entender mejor lo que está haciendo, el código inmediatamente inferior a este párrafo conseguiría el mismo resultado que poniendo @create_logger before say_hello.

def say_hello():
 print("¡Hola, mundo!")

say_hello = create_logger(say_hello)

En otras palabras, una forma de utilizar decoradores en Python es llamar explícitamente al decorador pasando la función como hicimos en el código anterior. La otra forma, más concisa, es utilizar la sintaxis @.

En esta sección, hemos cubierto cómo crear decoradores en Python.

Ejemplos algo más complicados

El ejemplo anterior era un caso sencillo. Hay ejemplos un poco más complejos como cuando la función que estamos decorando toma argumentos. Otra situación más complicada es cuando se quiere decorar una clase entera. Voy a cubrir ambas situaciones aquí.

Cuando la función toma en argumentos

Cuando la función que está decorando toma argumentos, la función modificada debe recibir los argumentos y pasarlos cuando eventualmente haga la llamada a la función no modificada. Si esto le parece confuso, permítame explicárselo en términos de foo-bar.

Recuerde que foo es la función decoradora, bar es la función que estamos decorando y baz es la barra decorada. En ese caso, el bar tomará los argumentos y se los pasará a baz durante la llamada a baz. He aquí un ejemplo de código para solidificar el concepto:

def foo(bar):
 def baz(*args, **kwargs):
       # Puede hacer algo aquí
 ___
 # Luego hacemos la llamada a bar, pasando args y kwargs
 bar(*args, **kwargs)
 # También puede hacer algo aquí
 ___

 return baz

Si los *args y **kwargs le resultan desconocidos; son simplemente punteros a los argumentos posicionales y de palabra clave, respectivamente.

Es importante tener en cuenta que baz tiene acceso a los argumentos y, por tanto, puede realizar alguna validación de los mismos antes de llamar a bar.

Un ejemplo sería si tuviéramos una función decoradora, ensure_string que se asegurara de que el argumento pasado a una función que está decorando es una cadena; la implementaríamos así

def ensure_string(func):
 def decorated_func(text):
 if type(text) is not str:
            raise TypeError('el argumento para ' func.__name__ ' debe ser una cadena.')
 else:
 func(text)

 return decorated_func

Podríamos decorar la función decir_hola así

@ensure_string
def decir_hola(nombre):
 print('Hola', nombre)

Luego podríamos probar el código usando esto

say_hello('John') # Debería ejecutarse sin problemas
say_hello(3) # Debería lanzar una excepción

Y debería producir la siguiente salida

Hola Juan
Traceback (most recent call last):
  File "/home/anesu/Documents/python-tutorial/./decorators.py", line 20, in <module> say hello(3) # debería lanzar una excepción
 File "/home/anesu/Documents/python-tu$ ./decorators.pytorial/./decorators.py", line 7, in decorated_func raise TypeError('el argumento de func._nombre_ debe ser una cadena.')
TypeError: el argumento de say hello debe ser una cadena. $0
Screenshot-from-2023-05-23-02-33-18

Como era de esperar, el script consiguió imprimir 'Hola Juan' porque 'Juan' es una cadena. Lanzó una excepción al intentar imprimir 'Hola 3' porque '3' no era una cadena. El decorador ensure_string podría utilizarse para validar los argumentos de cualquier función que requiera una cadena.

Decorando una clase

Además de sólo decorar funciones, también podemos decorar clases. Cuando añade un decorador a una clase, el método decorado sustituye al método constructor/iniciador de la clase (__init__).

Volviendo a foo-bar, supongamos que foo es nuestro decorador y Bar es la clase que estamos decorando, entonces foo decorará Bar.__init__. Esto será útil si queremos hacer algo antes de que los objetos de tipo Bar sean instanciados.

Esto significa que el siguiente código

def foo(func):
 def new_func(*args, **kwargs):
 print('Haciendo algunas cosas antes de la instanciación')
 func(*args, **kwargs)

 return new_func

@foo
class Bar:
 def __init__(self):
 print("En iniciador")

Es equivalente a

def foo(func):
 def new_func(*args, **kwargs):
 print('Haciendo algunas cosas antes de la instanciación')
 func(*args, **kwargs)

 return new_func

class Bar:
 def __init__(self):
 print("En iniciador")


Bar.__init__ = foo(Bar.__init__)

De hecho, instanciar un objeto de la clase Bar, definido utilizando cualquiera de los dos métodos, debería darle la misma salida:

Haciendo algunas cosas antes de la instanciación
En iniciador
Screenshot-from-2023-05-23-02-20-38

Ejemplos de decoradores en Python

Aunque puede definir sus propios decoradores, hay algunos que ya están incorporados en Python. Estos son algunos de los decoradores comunes que puede encontrar en Python:

@métodoestático

El método estático se utiliza en una clase para indicar que el método que está decorando es un método estático. Los métodos estáticos son métodos que pueden ejecutarse sin necesidad de instanciar la clase. En el siguiente ejemplo de código, creamos una clase Perro con un método estático ladrar.

clase Perro:
   @staticmethod
 def ladrar():
 print('¡Guau, guau!')

Ahora se puede acceder al método ladrar de la siguiente manera

Dog.bark()

Y la ejecución del código produciría la siguiente salida

¡Guau, guau!
Screenshot-from-2023-05-23-02-02-07

Como mencioné en la sección Cómo utilizar decoradores, los decoradores pueden utilizarse de dos formas. Siendo la sintaxis @ la más concisa de las dos. El otro método es llamar a la función del decorador, pasando la función que queremos decorar como argumento. Es decir, el código anterior consigue lo mismo que el código siguiente:

class Perro:
 def ladrar():
 print('¡Guau, guau!')

Perro.ladrar = staticmethod(Perro.ladrar)

Y podemos seguir utilizando el método ladrar de la misma manera

Dog.bark()

Y produciría la misma salida

¡Guau, guau!
Screenshot-from-2023-05-23-02-02-07-1

Como puede ver, el primer método es más limpio y es más obvio que la función es una función estática incluso antes de haber empezado a leer el código. Como resultado, para los ejemplos restantes, utilizaré el primer método. Pero recuerde que el segundo método es una alternativa.

@classmethod

Este decorador se utiliza para indicar que el método que está decorando es un método de clase. Los métodos de clase son similares a los métodos estáticos en que ambos no requieren que la clase sea instanciada antes de que puedan ser llamados.

Sin embargo, la principal diferencia es que los métodos de clase tienen acceso a los atributos de la clase mientras que los métodos estáticos no. Esto se debe a que Python pasa automáticamente la clase como primer argumento a un método de clase siempre que es llamado. Para crear un método de clase en Python, podemos utilizar el decorador classmethod.

clase Perro
   @classmethod
 def que_eres_tu(cls):
 print("¡Soy un " cls.__name__ "!")

Para ejecutar el código, simplemente llamamos al método sin instanciar la clase:

Perro.que_eres_tu()

Y la salida es

¡Soy un Perro!
Screenshot-from-2023-05-23-02-07-18

@propiedad

El decorador de propiedades se utiliza para etiquetar un método como definidor de propiedades. Volviendo a nuestro ejemplo del Perro, creemos un método que recupere el nombre del Perro.

clase Perro:
   # Creando un método constructor que tome el nombre del perro
 def __init__(self, nombre):

        # Creando una propiedad privada nombre
 # Los guiones bajos dobles hacen que el atributo sea privado
 self.__name = nombre

    
 @propiedad
 def nombre(self):
 return self.__name

Ahora podemos acceder al nombre del perro como a una propiedad normal,

# Creando una instancia de la clase
foo = Perro('foo')

# Accediendo a la propiedad nombre
print("El nombre del perro es:", foo.nombre)

Y el resultado de ejecutar el código sería

El nombre del perro es: foo
Screenshot-from-2023-05-23-02-09-42

@propiedad.setter

El decorador property.setter se utiliza para crear un método setter para nuestras propiedades. Para utilizar el decorador @property.setter, sustituya property por el nombre de la propiedad para la que está creando un decorador. Por ejemplo, si está creando un setter para el método de la propiedad foo, su decorador será @foo.setter. He aquí un ejemplo de Perro para ilustrarlo:

clase Perro:
   # Creando un método constructor que tome el nombre del perro
 def __init__(self, name):

        # Creando una propiedad privada nombre
 # Los guiones bajos dobles hacen que el atributo sea privado
 self.__name = nombre

    
 @propiedad
 def nombre(self):
 return self.__name

 # Creando un setter para nuestra propiedad nombre
 @nombre.setter
 def nombre(self, nuevo_nombre):
 self.__name = nuevo_nombre

Para probar el setter, podemos utilizar el siguiente código:

# Crear un nuevo perro
foo = Dog('foo')

# Cambiar el nombre del perro
foo.name = 'bar'

# Imprimir el nombre del perro en la pantalla
print("El nuevo nombre del perro es:", foo.name)

La ejecución del código producirá la siguiente salida

El nuevo nombre del perro es: bar
Screenshot-from-2023-05-23-11-46-25

Importancia de los decoradores en Python

Ahora que hemos cubierto lo que son los decoradores, y usted ha visto algunos ejemplos de decoradores, podemos discutir por qué los decoradores son importantes en Python. Los decoradores son importantes por varias razones. Algunas de ellas, las enumero a continuación:

  • Permiten la reutilización del código: En el ejemplo de registro dado anteriormente, podríamos utilizar el @create_logger en cualquier función que queramos. Esto nos permite añadir la funcionalidad de registro a todas nuestras funciones sin tener que escribirla manualmente para cada función.
  • Le permiten escribir código modular: De nuevo, volviendo al ejemplo del registro, con los decoradores, puede separar la función central, en este caso say_hello de la otra funcionalidad que necesita, en este caso, el registro.
  • Mejoran los marcos de trabajo y las bibliotecas: Los decoradores se utilizan ampliamente en los frameworks y librerías de Python para proporcionar funcionalidad adicional. Por ejemplo, en frameworks web como Flask o Django, los decoradores se utilizan para definir rutas, manejar la autenticación o aplicar middleware a vistas específicas.

Palabras finales

Los decoradores son increíblemente útiles; pueden utilizarse para ampliar funciones sin alterar su funcionalidad. Esto resulta útil cuando desea cronometrar el rendimiento de las funciones, registrar cada vez que se llama a una función, validar los argumentos antes de llamar a una función o verificar los permisos antes de que se ejecute una función. Una vez que entienda los decoradores, podrá escribir código de forma más limpia.

A continuación, puede que desee leer nuestros artículos sobre tuplas y el uso de cURL en Python.

  • Anesu Kafesu
    Autor
    Desarrollador web full stack y redactor técnico. Actualmente aprendiendo IA.
Gracias a nuestros patrocinadores
Más lecturas sobre desarrollo
Potencia tu negocio
Algunas de las herramientas y servicios que le ayudarán a hacer crecer su negocio.
  • Invicti utiliza el Proof-Based Scanning™ para verificar automáticamente las vulnerabilidades identificadas y generar resultados procesables en tan solo unas horas.
    Pruebe Invicti
  • Web scraping, proxy residencial, gestor de proxy, desbloqueador web, rastreador de motores de búsqueda, y todo lo que necesita para recopilar datos web.
    Pruebe Brightdata
  • Monday.com es un sistema operativo de trabajo todo en uno que te ayuda a gestionar proyectos, tareas, trabajo, ventas, CRM, operaciones, flujos de trabajo y mucho más.
    Prueba Monday
  • Intruder es un escáner de vulnerabilidades en línea que encuentra puntos débiles de ciberseguridad en su infraestructura, para evitar costosas violaciones de datos.
    Prueba Intruder