Decoradores en python ¿Qué son? + Python OOP

decoradores en pythonHoy voy a redactar sobre los decoradores en python. ¿Que coño es eso?

Recordamos que una función puede recibir como argumento para su parámetro otra función.

Un decorador es una función que recibe como argumento a otra función para devolver otra función..

Bien, podríamos decirlo también así:

Importante!
La función A toma como argumento la función B para devolver una función C

Los decoradores pueden definirse como estereotipos o patrones de diseño. Que permiten a una función (A) o clase de objeto (A) tomar otra función (B) como argumento para devolver una función (C). De esta manera obtendremos funciones dinámicas (que pueden cambiar) sin tener nosotros que cambiar su código fuente!

Un decorador es como un envoltorio con el cual envolvemos una función o una clase.

Bien ahora veamos esto más en profundidad y llevado a la práctica como a mi me gusta! Pero antes déjame aclararte que podemos dividir a los decoradores en grupos, los que permiten argumentos y los que no. Y también los que modifican la firma o signatura del método que decoran y los que no.

Ya te lié! Ya puedo vivir en paz.. Que no! Vamos a crear un decorador, venga!

Creando un decorador en python

 

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pythones.net
def decorador(func): #Creamos la función decorador (A) con el argumento func
    def nueva_funcion(): #Creamos la nueva función (C)
        print ("Perro dice:")#Añadimos una modificación a la función (B) dentro de (C)
        func() #Aquí estamos incluyendo la función (B) que le dimos como argumento a (A)

    return nueva_funcion #Para devolver (C)

@decorador #Decoramos la función
def saluda():
    print("Guau!")
saluda()

 

Diagrama de los decoradores en python

Resultado:

Perro dice:

Guau!

Como puedes ver en el diagrama la función Saluda() pasara por el decorador siempre dando como resultado una nueva función ejecutada que muestra lo que se encuentra dentro del decorador, es decir, la función Saluda() sale decorada!

A sería el decorador

B sería la función saluda

C sería la función nueva.

En este caso func() que brindamos como argumento será la función a la aplicaremos el decorador.

Bien espero que lo hallas entendido!! Ahora supongamos que definimos otra función bajo de este decorador, que en este caso sera Despedida().

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pythones.net
def decorador(func): #Creamos la función decorador (A)
    def nueva_funcion(): #Creamos la nueva función (C)
        print ("Perro dice:")#Añadimos una modificación a la función (B) dentro (C)
        func() #Aquí estamos incluyendo la función (B) que le dimos como argumento a (A)
                #Para crear (C)
    return nueva_funcion
@decorador #Decoramos la función
def saluda():
    print("Guau!")
def despedida():
    print ("Chau")
saluda()

despedida()

Resultado:

Perro dice:
Guau!
Chau

Como vez no vuelve a imprimir «Perro dice:» porque no se aplico el decorador, para eso debe volverse a colocar antes de la función Despedida() en este caso, así:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pythones.net
def decorador(func): #Creamos la funcion decorador (A)
    def nueva_funcion(): #Creamos la nueva funcion (C)
        print ("Perro dice:")#Añadimos una modificacion a la funcion (B) dentro (C)
        func() #Aqui estamos incluyendo la funcion (B) que le dimos como argumento a (A)
                
    return nueva_funcion #Para crear (C)
@decorador #Decoramos la función
def saluda(): #Con decorador
    print("Guau!")
@decorador
def despedida(): #Con decorador
    print ("Chau")
def movercola(): #Sin decorador
    print ("El perro mueve la cola")
saluda()

despedida()
    
movercola()

Resultado:

Perro dice:
Guau!
Perro dice:
Chau
El perro mueve la cola

Bueno, también agregue una función para que mueva la cola sin decorador para que vieras la diferencia.

Decoradores en clases de python – OOP

Ahora vamos a llevar esto para trabajar con clases y descubrir que representa un decorador y como lo aplicamos dentro de la programación orientada a objetos. Anteriormente te había mostrado algunos decoradores en variables de métodos de las clases. Pero ahora utilizamos los nuestros propios.

Seguimos con los perritos que ladran, pero en este caso vamos a crear una clase perro y dentro el método saluda con un mensaje personalizado. Por supuesto vamos a decorar este método utilizando el mismo decorador.

Ejemplo

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pythones.net
def decorador(func): # Damos como argumento del decorador a func
    def nueva_funcion(self, mensaje): #Aquí debemos colocar los parámetros con los que trabaja func
        print ("Perro dice:")#Código decorador
        func(self, mensaje) #En func agregamos los parámetros con los que trabaja

    return nueva_funcion #Retornamos la nueva función


class perro(object): #Creamos la clase heredando de object
    def __init__(self, nombre):  #Constructor con el atributo nombre
        self.nombre = nombre #Nombre es igual al argumento nombre etc.
    @decorador #Aqui antes del método se coloca el decorador!!!
    def saluda(self, mensaje): #Metodo saludar del perro, como siempre
        self.mensaje = mensaje #El parámetro de saluda mensaje es igual a mensaje arg..
        print(mensaje) #Imprimir el mensaje ATENCIÓN.
        print("Guau!") #Resto del código

maty = perro('Maty') #Instanciamos
maty.saluda('Uso Puppy Linux!') #Cuando llamamos al metodo saluda buscara añadirle el decorador
#Osea que se ira hasta arriba. Por ende allí también debimos incluir la instanciacion (self) y el
#Argumento mensaje para ambas (nueva_funcion) y func.

Resultado:

Perro dice:
Uso Puppy Linux!
Guau!

 

Excelente, pero si aun no has entendido lo volvemos a explicar mostrando el orden en el que trabaja el código. Solo para que tengas en cuenta los decoradores son justamente eso, una plantilla, una fabrica de ensamblar una función dentro de otra y devolver una mas gorda. Como viste en mi diagrama 😛

Fíjate como funciona con clases, casi igual solo que superpusimos una clase y nuestra función esta dentro como un método de esta clase.

Decoradores en clases de python

Ahora si lo entiendes!. Verdad?

Cada vez que se llame al metodo .saluda() primero se pasará por el decorador y el método saldrá modificado.

 

La dinámica de los decoradores

Pero acaso ¿no era que los decoradores nos permitían dinamizar estas funciones?, porque estas utilizando los parámetros en el decorador. ¿Y si usáramos el mismo decorador en otro método? ¿Estamos obligados a usar los mismos parámetros para la función func y nueva_funcion o que?.

Aquí con utilizar los mismos parámetros nos referimos a que si utilizáramos el mismo decorador en otro método, el método debería tener el parámetro mensaje. Por lo tanto no estamos dinamizando el decorador, solo sería aplicable a ese método. Pero hay una solución para esto:

En este caso fíjate que cambiamos el parámetro «mensaje» por «parametro1» y volvemos a utilizar el método decorado Saluda() y además añadimos otro método llamado Orden() que también es decorado con el mismo decorador.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# pythones.net
def decorador(func):
    def nueva_funcion(instancia, parametro1):
        print ("Perro dice:")
        func(instancia, parametro1) 

    return nueva_funcion 

class perro(object):
    def __init__(self, nombre): 
        self.nombre = nombre 
    @decorador 
    def saluda(self, mensaje): 
        self.mensaje = mensaje 
        print(mensaje)
        print("Guau!") 
    @decorador
    def ordeno(self, orden):
        self.orden = orden
        print(orden)
        print("La pata, la pata afgsad! Guau!")

maty = perro('Maty')
maty.saluda('Uso Puppy Linux!')
maty.ordeno('Doy la pata')

Resultado:

Perro dice:
Uso Puppy Linux!
Guau!
Perro dice:
Doy la pata
La pata, la pata afgsad! Guau!

Como ves cambiamos los parámetros de func() por solo parametro1 y self por instanciar. Agregamos otro método al decorador y lo llamamos también!. Todo ok, todo perfecto!!

Esta es la estructura correcta para usar decoradores en clases. Así que a partir de ahora recuerda esto a la hora de trabajar con clases porque te permitirá ahorrar mucho código no teniendo que volver a colocar todo ese código que esta dentro del decorador para cada método.

 

 

 


Usando clases como decoradores

 

Ahora te la voy a liar más.. Acaso, ¿no notas feo el uso de una función sobre una clase en el código anterior?, es muy desprolijo y estamos modificando un método desde fuera de la clase con una función a secas. ¿Qué tal si creara un decorador que es también una clase?

Como sabes en python todo es o puede ser un objeto. Una función, una clase pueden ser objetos también si así lo deseamos. Podemos utilizar una clase como decorador haciéndola llamable (invocable). Para ello debemos utilizar la función __call__ que nos permitirá emular un objeto como si fuese una función.

Así que venga sin más vueltas vamos a crear una clase que llamaremos «midecorador» de la siguiente manera:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  decoradores.py
class midecorador(object):
    def __init__(self, func): #Damos como parámetro una función
        print ("He construido la clase")
        func() #Llamamos a la función
        
    def __call__(self): #La definimos como llamable
        print ("Soy una clase llamada mediante call")
        


def hablar():
    print ("Hola soy la función hablar")

matias = midecorador(hablar) #instanciamos y llamamos la función hablar brindandola como argumento

Resultado:

He construido la clase 
Hola soy la función hablar

Instanciamos el objeto matias a la clase «midecorador» brindando como argumento la función que queremos decorar con esta clase. Y dicha función, recordemos en este caso es invocada dentro del init. Por lo que automáticamente se ejecutará con el constructor de la clase siempre que fuera creada una instancia de esta clase.

Pero también podemos recurrir al «@» para decorar sin instanciar.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  decoradores.py
class midecorador(object):
    def __init__(self, func): #Damos como parámetro una función
        print ("He construido la clase")
        func() #Llamamos a la función
        
    def __call__(self): #La definimos como llamable
        print ("Soy una clase llamada mediante call")
        

@midecorador
def hablar():
    print ("Hola soy la función hablar")

Resultado:

He construido la clase 
Hola soy la función hablar

Vemos que ni siquiera llamamos la función, sino que solo con decorarla al definirla esta es llamada automáticamente.

Pero si nosotros queremos que la misma no sea llamada automáticamente debemos almacenarla en el constructor y llamarla en el call.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  decoradores.py
class midecorador(object):
    def __init__(self, func): #Damos como parámetro una función
        print ("""He construido la clase""")
        self.func = func #La almacenamos en el constructor
        
        
    def __call__(self): #La definimos como llamable
        print ("Soy una clase llamada mediante call")
        self.func() #Ejecutamos la funcion en call
        

@midecorador
def hablar():
    print ("Hola soy la función hablar")

hablar() #Llamamos la función decorada

Resultado:

He construido la clase
Soy una clase llamada mediante call
Hola soy la función hablar

También podemos pasarle argumentos a esta función utilizando *args y **kargs en call.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
#  decoradores.py
class midecorador(object):
    def __init__(self, func): #Damos como parámetro una función
        print ("""He construido la clase""")
        self.func = func #La almacenamos en el constructor
        
        
    def __call__(self, *args, **kargs): #La definimos como llamable
        print ("Soy una clase llamada mediante call")
        self.func(*args, **kargs) #Ejecutamos la funcion en call
        

@midecorador
def hablar(mensaje):
    print (mensaje)

hablar("Soy un argumento para el parámetro mensaje") #Llamamos la función decorada

Resultado:

He construido la clase
Soy una clase llamada mediante call
Soy un argumento para el parámetro mensaje

 

De esta forma hemos aprendido a crear nuestros propios decoradores que nos permitirán añadir funcionalidad extra a una función.

Descansando en la nube: login offY esto es todo por hoy. Necesito dormir!! Espero te halla sido de utilidad y no critiques mis diagramas, que tu con el paint no eres Picasso! Un abrazo y no olvides compartir con aquellos interesados en programar. Nos vemos pronto en otra entrada!

 

 


 

Sigue leyendo!
Entrada anterior! Siguiente entrada!

Compartir es agradecer! :)

5 comentarios en “Decoradores en python ¿Qué son? + Python OOP”

  1. En el caso en que usamos las clases como decoradores, ¿Por qué cuando usamos el método __call__ no aparece su print » Soy una clase llamada mediente call»?
    Es en el primer ejemplo.

  2. Hola David y Jessica, en realidad no aparece porque estábamos llamando la función (func()) desde el __init__, y no desde call como sería en el último caso fijarse en linea 13 u 8 de los últimos ejemplos:

    13. self.func() #Ejecutamos la función en call

    Caso contrario solo es un print almacenado que no es ejecutado porque en los primeros ejemplos hemos instanciado y llamado la función directamente desde el constructor de la clase omitiendo la llamada.

  3. Buenas noches, para que sirven los signos de multiplicación en el call alado de args y kargs aqui delo el codigo:
    def __call__(self, *args, **kargs): #La definimos como llamable
    print («Soy una clase llamada mediante call»)
    self.func(*args, **kargs) #Ejecutamos la funcion en call

  4. Hola los símbolos «*», «**» al momento de pasar argumentos para los parámetros de funciones, clases, etc. Son «comodines» !
    Comodines en programación: Caracteres especiales que pueden representar caracteres desconocidos en un valor de texto y son prácticos para encontrar varios elementos con datos similares pero no idénticos. En una entrada lo explico, creo que aquí:
    https://pythones.net/programacion-en-python-fundamentos/#Parametros_y_argumentos_Posicionales_y_palabras_claves_en_Python
    Saludos

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.