Decoradores

Decoradores

Básicamente un decorador es una función que recibe una función como argumento y devuelve otra. o que involucra al menos 3 funciones, la recibida, la devuelta y la que procesa. La función "procesadora" lo que hace es modificar el comportamiento de la función recibida sin modificar su código, o lo que es lo mismo, extender las funcionalidades de la función original.

Para adentrarnos en el concepto de decoradores vamos a iniciar con un ejemplo extremadamente sencillo. En el siguiente ejemplo vamos a ver una función muy simple como ya hemos visto anterioridad, que simplemente imprime un mensaje por pantalla que dice "Buenos dias".

import os
os.system('cls')

def saludo():
    print('Buenos dias')

saludo()

Resultado:

Buenos dias

Para crear un decorador que expanda las posibilidades de la función saludo() basta con definir una función anidada la recibe como argumento dicha función saludo() y devuelve la función contenida a nivel de función contenedora. En este caso vamos a agregar un mensaje que indica el inicio y el fin de la ejecución de la función saludo(). Es importante destacar que las ampliaciones de capacidades de la función se encuentran dentro de la función contenida del decorador. Luego para invocar la función saludo() usando el decorador hay que escribir el nombre de la función decoradora antecedido por un arroba (@) en la linea anterior a la definición de la función a decorar. Veamos el siguiente ejemplo:

import os
os.system('cls')

def decorador(funcion):
    def decorar():
        print('Inicio de la funcion',funcion.__name__)
        funcion()
        print('Fin de la función', funcion.__name__)
    return decorar

@decorador
def saludo():
    print('Buenos dias')

saludo()

Resultado:

Inicio de la funcion saludo
Buenos dias
Fin de la función saludo

Como se puede deducir, para reutilizar la función decoradora, basta con su invocación utilizando el arroba (@) antes de la definición de la nueva función. Supongamos que tenemos otra función a la cual le deseamos aplicar el decorador ya creado para que indique el inicio y el fin  de dicha función:

import os
os.system('cls')

def decorador(funcion):
    def decorar():
        print('Inicio de la funcion',funcion.__name__)
        funcion()
        print('Fin de la función', funcion.__name__)
    return decorar

@decorador
def saludo():
    print('Buenos dias')
@decorador
def saludo_vespertino():
    print('Buenas tardes')

saludo()
saludo_vespertino()

Resultado:

Inicio de la funcion saludo
Buenos dias
Fin de la función saludo
Inicio de la funcion saludo_vespertino
Buenas tardes
Fin de la función saludo_vespertino

Podemos comprobar por el resultado, que se ha aplicado el decorador ya creado a la nueva función saludo_vespertino, sin necesidad de reescribir el código ya creado previamente.

Uso de argumentos en los decoradores

En el caso que la función a decorar pueda recibir argumentos (cosa que es muy probable en muchos casos), es necesario preparar el decorador para recibir N cantidad de parámetros para evitar un error de argumentos esperados y recibidos. Supongamos que ahora deseamos que la función saludo() reciba un nombre como argumento para personalizar el mensaje. Veamos el siguiente ejemplo:

import os
os.system('cls')

def decorador(funcion):
    def decorar():
        print('Inicio de la funcion',funcion.__name__)
        funcion()
        print('Fin de la función', funcion.__name__)
    return decorar

@decorador
def saludo(nombre):
    print(f'Buenos dias {nombre}')

saludo('César')

Resultado:

Traceback (most recent call last):
  File "e:\Documentos\Desarrollos\Python\Curso-Python\10 Funciones definidas por el usuario.py", line 335, in <module>
    saludo('César')
TypeError: decorador.<locals>.decorar() takes 0 positional arguments but 1 was given

como podemos ver el intérprete de Python arroja un error indicando que la función decoradora no tiene parámetros de entrada y sin embargo recibe un argumento. Es conveniente preparar la función decoradora para cualquier cantidad de argumentos, para lo cual podemos usar el operador de desempaquetamiento de objetos iterables (*) y/o el operador de desempaquetamiento de objetos iterables indexados (**) con el uso de la sintaxis args y/o kwargs según sea el caso. Veamos el siguiente ejemplo:

import os
os.system('cls')

def decorador(funcion):
    def decorar(*args, **kwargs):
        print('Inicio de la funcion',funcion.__name__)
        funcion(*args, **kwargs)
        print('Fin de la función', funcion.__name__)
    return decorar

@decorador
def saludo(nombre):
    print(f'Buenos dias {nombre}')

saludo('César')

Resultado:

Inicio de la funcion saludo
Buenos dias César
Fin de la función saludo

Como podemos ver el intérprete de Python ya no arroja error, y de hecho el código funciona y ahora la función decoradora admite argumentos indefinidos, que en este caso es una cadena de caracteres con el nombre a quien se le desea dar los buenos días. Ademas de funcionar correctamente la ampliación de las capacidades de la función decorada con los mensajes de inicio y fin de la ejecución de la función.

Comentarios

Entradas populares