Aunque los decoradores Python coinciden en el nombre con el Patron Decorador y guardan cierta similitud, no hay que confundirlos.
Mientras que el Patron Decorador permite añadir dinámicamente funcionalidad a un objeto, el objetivo principal de un decorador Python es añadir funcionalidad extra a una funcion. Para añadir funcionalidad hacemos uso de un wrapper. Un envoltorio, es decir, envolvemos la funcion. Para hacer esto, podemos usar clases u objetos.
Usando clases como decoradores Python
Para que esto funcione, el objeto devuelto por el decorador tiene que estar preparado para ser utilizado como una función. Esto significa que debe ser invocable. Para conseguir esto la clase debe implementar la función especial __call__ que permite emular un objeto como si fuera una función.
Ejemplo 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Decorador(object): def __init__(self, f): print "__init__()" f() def __call__(self): print "__call__()" def Funcion(): print "Funcion()" print "Inicio" Decorado = Decorador(Funcion) Decorado() print "Fin" |
1 2 3 4 5 | Inicio __init__() Funcion() __call__() Fin |
Para hacerlo menos engorroso, Python proporciona soporte para envolver una función en un decorador mediante el símbolo @.
Ejemplo 2. La función se invoca en __init__
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | class Decorador(object): def __init__(self, f): print "__init__()" f() def __call__(self): print "__call__()" @Decorador def Funcion(): print "Funcion()" print "Inicio" Funcion() print "Fin" |
1 2 3 4 5 | __init__() Funcion() Inicio __call__() Fin |
Ejemplo 3. Guarda la referencia en __init__ y se invoca en __call__
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Decorador(object): def __init__(self, f): print "__init__()" self._f = f def __call__(self): print "__call__()" self._f() @Decorador def Funcion(): print "Funcion()" print "Inicio" Funcion() print "Fin" |
1 2 3 4 5 | __init__() Inicio __call__() Funcion() Fin |
Ejemplo 4. Invocar y guarda el resultado de la función __init__ y hacer uso en __call__
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class Decorador(object): def __init__(self, f): print "__init__()" self._resultado = f() def __call__(self): print "__call__()" print "la funcion dijo", self._resultado @Decorador def Funcion(): print "Funcion()" return "Hola!" print "Inicio" Funcion() print "Fin" print |
1 2 3 4 5 6 | __init__() Funcion() Inicio __call__() la funcion dijo Hola! Fin |
Ejemplo 5. Invoca en __call__ y habilitar el paso de parámetros
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | class Decorador(object): def __init__(self, f): print "__init__()" self._f = f def __call__(self, *args, **kwargs): print "__call__()" self._f(*args, **kwargs) @Decorador def Funcion(saludo): print saludo, "desde la Funcion()" print "Inicio" Funcion("Hola!") print "Fin" |
1 2 3 4 5 | __init__() Inicio __call__() Hola! desde la Funcion() Fin |
Usando funciones como decoradores Python
Hemos visto que la única condición para que funcione un decorador es que éste sea invocable. En los ejemplos anteriores, hemos reemplazado la función original con un objeto de una clase que sobreescribe el método __call__. Podemos reescribir los ejemplos anteriores utilizando una función en lugar de una clase, aunque no da tanto juego.
Ejemplo 6. Generar un mensaje de bienvenida en formato HTML
1 2 3 4 5 6 7 8 9 10 11 | def html(func): contador = 0 def box(*args, **kwargs): return "%s" % (func(*args, **kwargs)) return box def saludo(nombre, visitas): return "Hola %s eres el visitante numero %d!" % (nombre, visitas) documento = html(saludo) print documento("David", 500) |
1 | Hola David eres el visitante numero 500! |
Como antes hemos visto, para hacerlo menos engorroso hacemos uso del símbolo @.
1 2 3 4 5 6 7 8 9 10 11 | def html(func): contador = 0 def box(*args, **kwargs): return "%s" % (func(*args, **kwargs)) return box @html def saludo(nombre, visitas): return "Hola %s eres el visitante numero %d!" % (nombre, visitas) print saludo("David", 500) |
Ejemplo 7. Un logger para registrar los parámetros pasados a la función
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | def logger(func): def box(*args, **kwargs): print "los argumentos son: %s, %s" % (args, kwargs) result = func(*args, **kwargs) return result return box @logger def suma(a, b): return a+b @logger def resta(a, b): return a-b print suma(3,2) print resta(3,2) |
1 2 3 4 | los argumentos son: (3, 2), {} 5 los argumentos son: (7, 3), {} 4 |
Ejemplo 8. Comprobar con ‘assert’ que el argumento es un entero
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | def require_int (func): def wrapper (arg): assert isinstance(arg, int) return func(arg) return wrapper @require_int def p1 (arg): print arg @require_int def p2(arg): print arg*2 p1(2) p2(4) p2("dos") p1("cuatro") |
1 2 3 4 5 6 7 8 | 2 8 Traceback (most recent call last): File "decorator.py", line 18, in p2("dos") File "decorator.py", line 3, in wrapper assert isinstance(arg, int) AssertionError |
Nota: por defecto los asserts están activados. Para desactivarlos hay que pasar el parámetro -O al interprete de comandos.
1 2 3 4 5 | > python -O decorator.py 2 8 dosdos cuatro |
Built-in Functions
Python incorpora algunas funciones útiles que pueden usarse como decoradores, por ejemplo classmethod, property y staticmethod
Ejemplo 9. uso de @staticmethod y @property para decorar funciones de una clase
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class Saludador: def __init__(self, nombre): self._nombre = nombre @staticmethod def saludo_generico(): print "Hola a todos!" def saludo_individual(self): print "Hola", self._nombre @property def nombre(self): return self._nombre test = Saludador("David") test.saludo_generico() Saludador.saludo_generico() test.saludo_individual() print "nombre", test.nombre |
1 2 3 4 | Hola a todos! Hola a todos! Hola David nombre David |
Os dejo un enlace con una presentación sobre decoradores avanzados en Python.
1 Pingback