Saltar a contenido
 No os perd谩is mi futuro contenido, seguidme en y Youtube 馃榿

Funciones decoradoras

No cabe duda de que Python es un lenguaje flexible, y cuando trabajamos con funciones no es una excepci贸n.

En Python, dentro de una funci贸n podemos definir otras funciones. Con la peculiaridad de que el 谩mbito de estas funciones se encuentre 煤nicamente dentro de la funci贸n padre. Vamos a trabajar los 谩mbitos un poco m谩s en profundidad:

Note

def hola():

    def bienvenido():
        return "Hola!"

    return bienvenido

Si intentamos llamar a la funci贸n bienvenido...

Note

bienvenido()
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-3-f083d151b813> in <module>()
----> 1 bienvenido()

NameError: name 'bienvenido' is not defined

Como vemos nos da un error de que no existe. En cambio si intentamos ejecutar la funci贸n hola():

Note

hola()
<function __main__.hola.<locals>.bienvenido>

Se devuelve la funci贸n bienvenido, y podemos apreciar dentro de su definici贸n que existe un espacio llamado locals, el cual hace referencia al 谩mbito local que abarca la funci贸n.

脕mbito local y global

Si utilizamos una funci贸n reservada locals() obtendremos un diccionario con todas las definiciones dentro del espacio local del bloque en el que estamos:

Note

def hola():

    def bienvenido():
        return "Hola!"

    print( locals() )  # Mostramos el 谩mbito local

hola()
{'bienvenido': <function hola.<locals>.bienvenido at 0x000001F867E88C80>}

Como vemos se nos muestra un diccionario, aqu铆 encontraremos la funci贸n bienvenido().

Podr铆amos a帽adir algo m谩s:

Note

lista = [1,2,3]

def hola():

    numero = 50

    def bienvenido():
        return "Hola!"

    print( locals() )  # Mostramos el 谩mbito local

hola()
{'bienvenido': <function hola.<locals>.bienvenido at 0x000001F867E88950>, 'numero': 50}

Como podemos observar, ahora adem谩s de la funci贸n tenemos una clave con el n煤mero y el valor 50. Sin embargo no encontramos la lista, pues esta se encuentra fuera del 谩mbito local. De hecho se encuentra en el 谩mbito global, el cual podemos mostrar con la funci贸n reservada globals():

Note

# Antes de ejecutar el bloque reinicia el Notebook para vaciar la memoria
lista = [1,2,3]

def hola():

    numero = 50

    def bienvenido():
        return "Hola!"

    print( globals() )  # Mostramos el 谩mbito global

hola()
{'__name__': '__main__', '__doc__': 'Automatically created module for IPython interactive environment', '__package__': None, '__loader__': None, '__spec__': None, '__builtin__': <module 'builtins' (built-in)>, '__builtins__': <module 'builtins' (built-in)>, '_ih': ['', '# Antes de ejecutar este bloque reinicia el Notebook para vaciar la memoria.\nlista = [1,2,3]\n\ndef hola():\n    \n    numero = 50\n    \n    def bienvenido():\n        return "Hola!"\n    \n    print( globals() )  # Mostramos el 谩mbito global\n\nhola()'], '_oh': {}, '_dh': ['C:\\Users\\hcost\\CursoPython\\Fase 4 - Temas avanzados\\Tema 15 - Funcionalidades avanzadas\\Apuntes'], 'In': ['', '# Antes de ejecutar este bloque reinicia el Notebook para vaciar la memoria.\nlista = [1,2,3]\n\ndef hola():\n    \n    numero = 50\n    \n    def bienvenido():\n        return "Hola!"\n    \n    print( globals() )  # Mostramos el 谩mbito global\n\nhola()'], 'Out': {}, 'get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x000001D9380544A8>>, 'exit': <IPython.core.autocall.ZMQExitAutocall object at 0x000001D9380B4BA8>, 'quit': <IPython.core.autocall.ZMQExitAutocall object at 0x000001D9380B4BA8>, '_': '', '__': '', '___': '', '_i': '', '_ii': '', '_iii': '', '_i1': '# Antes de ejecutar este bloque reinicia el Notebook para vaciar la memoria.\nlista = [1,2,3]\n\ndef hola():\n    \n    numero = 50\n    \n    def bienvenido():\n        return "Hola!"\n    \n    print( globals() )  # Mostramos el 谩mbito global\n\nhola()', 'lista': [1, 2, 3], 'hola': <function hola at 0x000001D9381A3A60>}

Tampoco es necesario que nos paremos a analizar el contenido, pero como podemos observar, desde el 谩mbito global tenemos acceso a muchas m谩s definiciones porque engloba a su vez todas las de sus bloques padres.

Si mostramos 煤nicamente las claves del diccionario globals(), quiz谩 ser铆a m谩s entendible:

Note

globals().keys()
dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__builtin__', '__builtins__', '_ih', '_oh', '_dh', 'In', 'Out', 'get_ipython', 'exit', 'quit', '_', '__', '___', '_i', '_ii', '_iii', '_i1', 'lista', 'hola', '_i2'])

Ahora si buscamos bien encontraremos la clave lista, la cual hace referencia a la variable declarada fuera de la funci贸n. Incluso podr铆amos acceder a ella como si fuera un diccionario normal:

Note

# Desde la funci贸n globals
globals()['lista']  
[1, 2, 3]

Note

# Forma tradicional
lista  
[1, 2, 3]

Funciones como variables

Volviendo a nuestra funci贸n hola(), ahora sabemos que si la ejecutamos, en realidad estamos accediendo a su funci贸n local bienvenido(), pero eso no significa que la ejecutamos, s贸lo estamos haciendo referencia a ella.

Esa es la raz贸n de que se devuelva su definici贸n y no el resultado de su ejecuci贸n:

Note

def hola():

    def bienvenido():
        return "Hola!"

    return bienvenido

hola()
<function __main__.hola.<locals>.bienvenido>

Por muy raro que parezca, podr铆amos ejecutarla llamando una segunda vez al par茅ntesis. La primera para hola() y la segunda para bienvenido():

Note

hola()()
'Hola!'

Como es realmente extra帽o, normalmente lo que hacemos es asignar la funci贸n a una variable y ejecutarla como si fuera una nueva funci贸n:

Note

bienvenido = hola()
bienvenido()
'Hola!'

A diferencia de las colecciones y los objetos, donde las copias se utilizaban como accesos directos, las copias de las funciones son independientes y aunque borr谩semos la original, la nueva copia seguir铆a existiendo:

Note

del(hola)
bienvenido()
'Hola!'

Funciones como argumentos

Si ya era extra帽o ejecutar funciones anidadas, todav铆a es m谩s extra帽o el concepto de enviar una funci贸n como argumento de otra funci贸n, sin embargo gracias a la flexibilidad de Python es posible hacerlo:

Note

def hola():
    return "Hola!"

def test(funcion):
    print( funcion() )

test(hola)
Hola Mundo

Quiz谩 en este momento no se ocurren muchas utilidades para esta funcionalidad, pero creedme que es realmente 煤til cuando queremos extender funciones ya existentes sin modificarlas. De ah铆 que este proceso se conozca como un decorador, y de ah铆 pasamos directamente a las funciones decoradoras.

Funciones decoradoras

Una funci贸n decoradora es una funci贸n que envuelve la ejecuci贸n de otra funci贸n y permite extender su comportamiento. Est谩n pensadas para reutilazarlas gracias a una sintaxis de ejecuci贸n mucho m谩s simple.

Imaginaros estas dos funciones sencillas:

Note

def hola():
    print("Hola!")

def adios():
    print("Adi贸s!")

Y queremos queremos crear un decorador para monitorizar cuando se ejecutan las dos funciones, avisando antes y despu茅s.

Para crear una funci贸n decoradora tenemos que recibir la funci贸n a ejecutar, y envolver su ejecuci贸n con el c贸digo a extender:

Note

def monitorizar(funcion):

    def decorar():
        print("\t* Se est谩 apunto de ejecutar la funci贸n:", 
            funcion.__name__)
        funcion()
        print("\t* Se ha finalizado de ejecutar la funci贸n:", 
            funcion.__name__)

    return decorar

def adios():
    print("Adi贸s!")

Ahora para realizar la monitorizaci贸n deber铆amos llamar al monitor ejecutando la funci贸n enviada y devuelta:

Note

monitorizar(hola)()
    * Se est谩 apunto de ejecutar la funci贸n: hola
Hola!
    * Se ha finalizado de ejecutar la funci贸n: hola

Sin embargo esto no es muy c贸modo, y ah铆 es cuando aparece la sintaxis que nos permite configurar una funci贸n decoradora en una funci贸n normal:

Note

@monitorizar
def hola():
    print("Hola!")

@monitorizar
def adios():
    print("Adi贸s!")

Una vez configurada la funci贸n decoradora, al utilizar las funciones se ejecutar谩n autom谩ticamente dentro de la funci贸n decoradora:

Note

hola()
print()
adios()
    * Se est谩 apunto de ejecutar la funci贸n: hola
Hola!
    * Se ha finalizado de ejecutar la funci贸n: hola

    * Se est谩 apunto de ejecutar la funci贸n: adios
Adi贸s!
    * Se ha finalizado de ejecutar la funci贸n: adios

Paso de argumentos

Note

def monitorizar_args(funcion):

    def decorar(*args,**kwargs):
        print("\t* Se est谩 apunto de ejecutar la funci贸n:", 
            funcion.__name__)
        funcion(*args,**kwargs)
        print("\t* Se ha finalizado de ejecutar la funci贸n:", 
            funcion.__name__)

    return decorar

@monitorizar_args
def hola(nombre):
    print("Hola {}!".format(nombre))

@monitorizar_args
def adios(nombre):
    print("Adi贸s {}!".format(nombre))

hola("H茅ctor")
print()
adios("H茅ctor")
    * Se est谩 apunto de ejecutar la funci贸n: hola
Hola H茅ctor!
    * Se ha finalizado de ejecutar la funci贸n: hola

    * Se est谩 apunto de ejecutar la funci贸n: adios
Adi贸s H茅ctor!
    * Se ha finalizado de ejecutar la funci贸n: adios

Ahora ya sabes qu茅 son las funciones decoradoras y c贸mo utilizar el s铆mbolo @ para automatizar su ejecuci贸n. Estas funciones se utilizan mucho cuando trabajamos con Frameworks Web como Django, as铆 que seguro te har谩n servicio si tienes pensado aprender a utilizarlo.


脷ltima edici贸n: 6 de Octubre de 2018