¿Por Qué Los Decoradores En Python Son Pura Genialidad?

Rhea Moutafis
Jun 04, 2021

¿Por Qué Los Decoradores En Python Son Pura Genialidad?

Jun 04, 2021 8 minutes read

Analiza, prueba y reutiliza tu código con poco más que un símbolo @

Si hay algo que hace que Python sea increíblemente exitoso, eso sería su legibilidad. Todo lo demás depende de eso: si el código es ilegible, es difícil de mantener. Tampoco es fácil para los principiantes: un novato que se aturda con un código ilegible no intentará escribir el suyo propio algún día.

Python ya era legible y amigable para los principiantes antes de que aparecieran los decoradores. Pero a medida que el lenguaje comenzó a ser utilizado para más y más cosas, los desarrolladores de Python sintieron la necesidad de más y más características, sin desordenar el paisaje y hacer el código ilegible.

Los decoradores son un ejemplo de una característica perfectamente implementada. Lleva un tiempo entenderlos, pero vale la pena. Cuando empieces a usarlos, notarás cómo no complican demasiado las cosas y hacen que tu código sea limpio y elegante.


Antes que nada: funciones de orden superior


En pocas palabras, los decoradores son una forma clara de manejar las funciones de orden superior. Así que vamos a verlos primero.

Funciones que devuelven funciones

Digamos que tienes una función, greet() - que saluda a cualquier objeto que le pases. Y digamos que tienes otra función, simon() - que inserta "Simón" donde sea apropiado. ¿Cómo podemos combinar las dos? Piénsalo un momento antes de mirar a continuación



def greet(name):
    return f"Hello, {name}!"
def simon(func):
    return func("Simon")
simon(greet)

La salida es "¡Hola, Simon!". Espero que tenga sentido para ti.

Por supuesto, podríamos haber llamado simplemente a greet("Simon"). Sin embargo, la cuestión es que podríamos querer poner "Simón" en muchas funciones diferentes. Y si no usamos "Simón" sino algo más complicado, podemos ahorrar un montón de líneas de código empaquetándolo en una función como simon().

Funciones dentro de otras funciones

También podemos definir funciones dentro de otras funciones. Esto es importante porque los decoradores también lo hacen. Sin los decoradores se ve así:



def respect(maybe):
    def congrats():
        return "Congrats, bro!"
    def insult():
        return "You're silly!"
    if maybe == "yes":
        return congrats
    else:
        return insult


La función respect() devuelve una función; respect("yes") devuelve la función congrats, respect("brother") (o algún otro argumento en lugar de "brother") devuelve la función insultos. Para llamar a las funciones, introduce respect("sí")() y respect("hermano")(), como una función normal.

Lee también:  Python Books You Must Read in 2020

¿Lo tienes? ¡Entonces ya tienes todo listo para los decoradores!

Code is beautifully nerdy. Image by author.



El ABC de los decoradores de Python


Funciones con un símbolo @

Probemos una combinación de los dos conceptos anteriores: una función que toma otra función y define una función. ¿Suena alucinante? Considera esto:


def startstop(func):
    def wrapper():
        print("Starting...")
        func()
        print("Finished!")
    return wrapper
def roll():
    print("Rolling on the floor laughing XD")
roll = startstop(roll)


La última línea asegura que ya no necesitamos llamar a startstop(roll)(); con roll() será suficiente. ¿Sabes cuál es la salida de esa llamada? ¡Pruébalo tú mismo si no estás seguro!

Ahora, como una muy buena alternativa, podríamos insertar esto justo después de definir startstop():


@startstop
def roll():
    print("Rolling on the floor laughing XD")

Esto hace lo mismo, pero pega roll() a startstop() al principio.


Flexibilidad añadida

¿Por qué es útil? ¿No consume exactamente las mismas líneas de código que antes?

En este caso, sí. Pero una vez que estás tratando con cosas un poco más complicadas, se vuelve realmente útil. Por una vez, puedes mover todos los decoradores (es decir, la parte def startstop() de arriba) a su propio módulo. Es decir, los escribes en un archivo llamado decorators.py y escribes algo como esto en tu archivo principal:

from decorators import startstop
@startstop
def roll():
    print("Rolling on the floor laughing XD")


En principio, puedes hacerlo sin usar decoradores. Pero de esta manera se hace la vida más fácil porque ya no tienes que lidiar con funciones anidadas y con el interminable conteo de corchetes.

También puedes anidar decoradores:

from decorators import startstop, exectime
@exectime
@startstop
def roll():
    print("Rolling on the floor laughing XD")


Ten en cuenta que aún no hemos definido exectime(), pero lo verás en la siguiente sección. Es una función que puede medir el tiempo que tarda un proceso en Python.

Este anidamiento equivaldría a una línea como esta

roll = exectime(startstop(roll))

¡Comienza el conteo de corchetes! Imagina que tienes cinco o seis de esas funciones anidadas unas dentro de otras. ¿No sería la notación del decorador mucho más fácil de leer que este lío anidado?

Incluso puedes usar decoradores en funciones que aceptan argumentos. Ahora imagina unos cuantos argumentos en la línea anterior y tu caos estaría completo. Los decoradores lo hacen más limpio y ordenado.

Lee también: How to Get a Job With Python

Finalmente, puedes incluso añadir argumentos a tus decoradores - como @mydecorator(argument). Sí, puedes hacer todo esto sin decoradores. Pero entonces te deseo que te diviertas entendiendo tu código sin decoradores cuando lo releas dentro de tres semanas...

Decorators make everything easier. Image by author.




Aplicaciones: donde los decoradores cortan la nata


Ahora que espero haberte convencido de que los decoradores te hacen la vida tres veces más fácil, veamos algunos ejemplos clásicos en los que los decoradores son básicamente indispensables.

Medir el tiempo de ejecución

Supongamos que tenemos una función llamada waste time() y queremos saber cuánto tarda. Pues bien, ¡sólo hay que utilizar un decorador!

import time
def measuretime(func):
    def wrapper():
        starttime = time.perf_counter()
        func()
        endtime = time.perf_counter()
        print(f"Time needed: {endtime - starttime} seconds")
    return wrapper
@measuretime
def wastetime():
    sum([i**2 for i in range(1000000)])
wastetime()

Una docena de líneas de código y ya está. Además, puedes usar measuretime() en tantas funciones como quieras.


Ralentizar el código

A veces no quieres ejecutar el código inmediatamente, sino esperar un tiempo. Ahí es donde un decorador de ralentización resulta útil:

import time
def sleep(func):
    def wrapper():
        time.sleep(300)
        return func()
    return wrapper
@sleep
def wakeup():
    print("Get up! Your break is over.")
wakeup()


Llamar a wakeup() hace que te tomes un descanso de 5 minutos, tras el cual tu consola te recuerda que debes volver al trabajo.

Lee también: Building A Linear Regression Model With Python To Predict Retail Customer Spending

Pruebas y depuración

Digamos que tienes un montón de funciones diferentes que llamas en diferentes etapas, y estás perdiendo la visión general sobre lo que se está llamando cuando. Con un simple decorador para cada definición de función, puedes aportar más claridad. Así:

def debug(func):
    def wrapper():
        print(f"Calling {func.__name__}")
    return wrapper
@debug
def scare():
    print("Boo!")
scare()


Hay un ejemplo mucho más elaborado aquí. Ten en cuenta, sin embargo, que para entender ese ejemplo, tendrás que comprobar cómo decorar funciones con argumentos. Aun así, ¡vale la pena leerlo!

Reutilización de código

Esto no hace falta decirlo. Si has definido una función decorator(), puedes esparcir @decorator por todo tu código. Para ser honesto, no creo que haya nada más simple que eso.

Manejo de los inicios de sesión

Si tienes funcionalidades a las que sólo se puede acceder si un usuario está conectado, también es bastante fácil con los decoradores. Te remitiré al ejemplo completo para que lo consultes, pero el principio es bastante sencillo: primero defines una función como login_required(). Antes de cualquier definición de función que necesite el inicio de sesión, pones @login_required. Bastante simple, diría yo.


Azúcar sintáctico - o por qué Python es tan dulce


No es que no sea crítico con Python o que no utilice lenguajes alternativos cuando sea apropiado. Pero hay un gran atractivo en Python: es muy fácil de digerir, incluso cuando no eres un informático de formación y sólo quieres hacer que las cosas funcionen.

Si C++ es una naranja, Python es una piña: igualmente nutritiva, pero tres veces más dulce. Los decoradores son sólo un factor en la mezcla.

Pero espero que hayas llegado a ver por qué es un factor tan dulce. ¡Azúcar sintáctico para añadir algo de placer a tu vida! Sin riesgos para la salud, salvo tener los ojos pegados a una pantalla.

¡Te deseo mucho código dulce!

Lee también: How to Use Python Datetimes Correctly?
Join our private community in Discord

Keep up to date by participating in our global community of data scientists and AI enthusiasts. We discuss the latest developments in data science competitions, new techniques for solving complex challenges, AI and machine learning models, and much more!