Decoradores
Contents
Decoradores#
Introducción#
Los decoradores (decorators) constituyen un patrón de programación que se utiliza cuando es necesario incluir un comportamiento adicional a objetos específicos. Una forma de agregar tal comportamiento adicional es decorar los objetos creados con tipos que aportan la funcionalidad extra.
Estos decoradores envuelven el objeto original pero presentan exactamente la misma interfaz para el usuario de ese objeto. Por lo tanto, el patrón de diseño del decorador extiende el comportamiento de un objeto sin utilizar subclassing. La decoración de un objeto es transparente a los clientes de los decoradores.
En Python, los decoradores son funciones que toman otra función (u otro objeto invocable como un método) y devuelve una tercera función que representa el comportamiento decorado.
Decoradores#
Definición de un decorador#
Para definir un decorador, debe definir un objeto invocable, como una función que toma otra función como parámetro y devuelve una nueva función. A continuación se da un ejemplo de la definición de una función decoradora de registro (logger) muy simple
def logger(func):
def inner():
print('llamando ', func.__name__)
func()
print('llamada', func.__name__)
return inner
Observe que la función logger retorna una función, inner, la cual a su vez llamará a una tercera función func.
Usando el decorador#
Veamos ahora el efecto del decorador en acción. Usaremos la función target como la función que vamos a decorar.
def target():
print('Dentro de la función target')
t1 = logger(target)
t1()
llamando target
Dentro de la función target
llamada target
Suavizando el trabajo de definición de un decorador#
Python proporciona algo de azúcar sintáctico que permite decorar directamente la función desde su definición. Este es el uso más practico de los decoradores.
@logger
def target():
print('Dentro de la función target')
target()
llamando target
Dentro de la función target
llamada target
Funciones con parámetros#
En este caso la función decoradora debe incluir los parámetros. Veamos el ejemplo.
def logger(func):
def inner(x, y):
print('llamando ', func.__name__, 'con ',x , 'y',y)
func(x, y)
print('regresando de ',func.__name__)
return inner
@logger
def mi_funcion(x, y):
print('x + y = ', x+y)
mi_funcion(5,6)
llamando mi_funcion con 5 y 6
x + y = 11
regresando de mi_funcion
Decoradores apilados (stacked decorators)#
Es posible apilar decoradores. Veamos el ejemplo. Vamos a imprimir un texto. Los decoradores agregaran negrilla (bold) e itálica (italic) al texto impreso.
# decoradores
def make_bold(fn):
def makebold_wrapper():
return "<b>" + fn() + "</b>"
return makebold_wrapper
def make_italic(fn):
def makeitalic_wrapper():
return "<i>" + fn() + "</i>"
return makeitalic_wrapper
# aplica los decoradores
@make_bold
@make_italic
def hello():
return 'hola mundo'
print(hello())
<b><i>hola mundo</i></b>
Decoradores para métodos de clases#
En este caso, es importante recordar que los métodos toman el parámetro especial self como el primer parámetro que se utiliza para hacer referencia al objeto del que se está aplicando el método. Por lo tanto, es necesario que el decorador tome este parámetro en cuenta; es decir, la función envuelta interna debe tomar al menos un parámetro que representa a self. Veamos el ejemplo.
def pretty_print(method):
def method_wrapper(self):
return "<p>{0}</p>".format(method(self))
return method_wrapper
class Persona:
def __init__(self, nombre, apellido, edad):
self.nombre = nombre
self.apellido = apellido
self.edad = edad
def print_self(self):
print('Persona - ', self.nombre, ', ', self.edad)
@pretty_print
def get_nombre_completo(self):
return self.nombre + " " + self.apellido
print('Comenzamos')
p = Persona('Alvaro', 'Montenegro', 61)
p.print_self()
print(p.get_nombre_completo())
print('Hecho!')
Comenzamos
Persona - Alvaro , 61
<p>Alvaro Montenegro</p>
Hecho!
Decoradores para métodos de clases con parámetros#
Aquí combinamos lo hecho en las dos subsecciones anteriores. Veamos.
def trace(method):
def method_wrapper(self, x, y):
print('Llamando', method.__name__, 'con', x, y)
method(self, x, y)
print('Se llamó', method.__name__, 'con', x, y)
return method_wrapper
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
@trace
def move_to(self, x, y):
self.x = x
self.y = y
def __str__(self):
return 'Point - ' + str(self.x) + ',' + str(self.y)
p = Point(1, 1)
print(p)
p.move_to(5,5)
print(p)
Point - 1,1
Llamando move_to con 5 5
Se llamó move_to con 5 5
Point - 5,5