Decoradores de pitón

Decoradores en Python

Python tiene una característica interesante llamada decoradores para agregar funcionalidad a un código existente.

Esto también se llama metaprogramación porque una parte del programa intenta modificar otra parte del programa en tiempo de compilación.


Requisitos previos para aprender decoradores

Para entender acerca de los decoradores, primero debemos saber algunas cosas básicas en Python.

Debemos sentirnos cómodos con el hecho de que todo en Python (¡Sí! Incluso las clases) son objetos. Los nombres que definimos son simplemente identificadores vinculados a estos objetos. Las funciones no son una excepción, también son objetos (con atributos). Se pueden vincular varios nombres diferentes al mismo objeto de función.

Aquí hay un ejemplo.

def first(msg):
    print(msg)


first("Hello")

second = first
second("Hello")

Producción

Hello
Hello

Cuando ejecuta el código, ambas funciones first y second dar la misma salida. Aquí, los nombres first y second se refieren al mismo objeto de función.

Ahora las cosas empiezan a ponerse más raras.

Las funciones se pueden pasar como argumentos a otra función.

Si ha utilizado funciones como map, filter y reduce en Python, entonces ya sabes sobre esto.

Las funciones que toman otras funciones como argumentos también se llaman funciones de orden superior. Aquí hay un ejemplo de tal función.

def inc(x):
    return x + 1


def dec(x):
    return x - 1


def operate(func, x):
    result = func(x)
    return result

Invocamos la función de la siguiente manera.

>>> operate(inc,3)
4
>>> operate(dec,3)
2

Además, una función puede devolver otra función.

def is_called():
    def is_returned():
        print("Hello")
    return is_returned


new = is_called()

# Outputs "Hello"
new()

Producción

Hello

Aquí, is_returned() es una función anidada que se define y devuelve cada vez que llamamos is_called().

Finalmente, debemos saber sobre Closures en Python.


Volviendo a Decoradores

Las funciones y los métodos se llaman invocable como se les puede llamar.

De hecho, cualquier objeto que implemente el especial __call__() método se denomina invocable. Entonces, en el sentido más básico, un decorador es un invocable que devuelve un invocable.

Básicamente, un decorador toma una función, agrega alguna funcionalidad y la devuelve.

def make_pretty(func):
    def inner():
        print("I got decorated")
        func()
    return inner


def ordinary():
    print("I am ordinary")

Cuando ejecuta los siguientes códigos en shell,

>>> ordinary()
I am ordinary

>>> # let's decorate this ordinary function
>>> pretty = make_pretty(ordinary)
>>> pretty()
I got decorated
I am ordinary

En el ejemplo que se muestra arriba, make_pretty() es decorador En el paso de asignación:

pretty = make_pretty(ordinary)

La función ordinary() se decoró y la función devuelta recibió el nombre pretty.

Podemos ver que la función de decorador agregó algunas funciones nuevas a la función original. Esto es similar a empacar un regalo. El decorador actúa como envoltorio. La naturaleza del objeto que se decoró (regalo real en el interior) no se altera. Pero ahora, se ve bonito (desde que se decoró).

Generalmente, decoramos una función y la reasignamos como,

ordinary = make_pretty(ordinary).

Esta es una construcción común y por esta razón, Python tiene una sintaxis para simplificar esto.

Podemos usar el @ símbolo junto con el nombre de la función decoradora y colóquelo encima de la definición de la función a decorar. Por ejemplo,

@make_pretty
def ordinary():
    print("I am ordinary")

es equivalente a

def ordinary():
    print("I am ordinary")
ordinary = make_pretty(ordinary)

Esto es solo un azúcar sintáctico para implementar decoradores.


Decorar funciones con parámetros

El decorador anterior era simple y solo funcionaba con funciones que no tenían ningún parámetro. ¿Qué pasaría si tuviéramos funciones que tomaran parámetros como:

def divide(a, b):
    return a/b

Esta función tiene dos parámetros, a y b. Sabemos que dará error si pasamos b como 0.

>>> divide(2,5)
0.4
>>> divide(2,0)
Traceback (most recent call last):
...
ZeroDivisionError: division by zero

Ahora hagamos que un decorador verifique este caso que causará el error.

def smart_divide(func):
    def inner(a, b):
        print("I am going to divide", a, "and", b)
        if b == 0:
            print("Whoops! cannot divide")
            return

        return func(a, b)
    return inner


@smart_divide
def divide(a, b):
    print(a/b)

Esta nueva implementación volverá None si surge la condición de error.

>>> divide(2,5)
I am going to divide 2 and 5
0.4

>>> divide(2,0)
I am going to divide 2 and 0
Whoops! cannot divide

De esta manera podemos decorar funciones que toman parámetros.

Un observador entusiasta notará que los parámetros del anidado inner() La función dentro del decorador es la misma que los parámetros de las funciones que decora. Teniendo esto en cuenta, ahora podemos hacer decoradores generales que funcionen con cualquier cantidad de parámetros.

En Python, esta magia se hace como function(*args, **kwargs). De este modo, args será la tupla de argumentos posicionales y kwargs será el diccionario de argumentos de palabras clave. Un ejemplo de tal decorador será:

def works_for_all(func):
    def inner(*args, **kwargs):
        print("I can decorate any function")
        return func(*args, **kwargs)
    return inner

Encadenamiento de decoradores en Python

Se pueden encadenar varios decoradores en Python.

Es decir, una función se puede decorar varias veces con decoradores diferentes (o iguales). Simplemente colocamos los decoradores encima de la función deseada.

def star(func):
    def inner(*args, **kwargs):
        print("*" * 30)
        func(*args, **kwargs)
        print("*" * 30)
    return inner


def percent(func):
    def inner(*args, **kwargs):
        print("%" * 30)
        func(*args, **kwargs)
        print("%" * 30)
    return inner


@star
@percent
def printer(msg):
    print(msg)


printer("Hello")

Producción

******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Hello
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************

La sintaxis anterior de,

@star
@percent
def printer(msg):
    print(msg)

es equivalente a

def printer(msg):
    print(msg)
printer = star(percent(printer))

El orden en el que encadenamos a los decoradores es importante. Si hubiéramos invertido el orden como,

@percent
@star
def printer(msg):
    print(msg)

La salida sería:

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
******************************
Hello
******************************
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Publicaciones Similares

Deja una respuesta

Tu dirección de correo electrónico no será publicada.