Un decorador (decorator, en inglés) es una función de orden superior, esto es, cualquier función que reciba como parámetro otra función, como las funciones map(), lambda, filter(), reduce(), etc. del mismo modo que una función que no recibe función alguna como parámetro se la clasifica como de primer orden, como casi la totalidad de los métodos de clase de datos (strings, listas, diccionarios, etc.) o las funciones built-in len(), type(), sorted(), etc.
Con lo que acabamos de decir ya sabemos que un decorador es una función (un objeto callable, para ir enlazando ideas y conceptos con una entrada anterior: callable, y que lleva como parámetro obligatorio otra función.
Esto nos proporciona un formato de sintaxis del tipo:
Una de sus características más singulares es que devuelve otra función: su return es otra función. No es un dato/valor del tipo que sea: entero, cadena, objeto de tipo date, bytearray, .... ¡NO! Devuelve otra función que da gusto verla. Y como una función no es un objeto printable como lo es cualquier dato o valor como los que mencionábamos antes, no podemos usar con un función decoradora la función print() para que nos muestre el resultado sino sólo la cláusula return.
La función devuelta puede ser:
- La propia función que se ha pasado como parámetro tal cual.
- La propia función que se ha pasado como parámetro con ciertas modificaciones y/o añadidos.
- Una función diferente.
Con lo que acabamos de exponer todavía podemos perfilar mejor lo que es un decorador o función decoradora, como también se conoce al asunto.
Un decorador es una función que lleva como parámetro obligatorio en su zona de parámetros a otra función, a la que puede sumar modificaciones y/o añadidos, nuevas funcionalidades, por ejemplo, a esa misma función que pasamos como parámetro, y que nos devuelve la función que es capaz de ejecutar esas nuevas modificaciones y/o añadidos.
¡VAYA GALIMATÍAS!, ¿EH? NO PASA NADA: LO ANTERIOR ES UNA DEFINICIÓN DE decorator POR "LAS BRAVAS", PERO ES ALGO QUE, COMO SUCEDE CON CASI TODO EN PROGRAMACIÓN, SE ENTIENDE MUCHO MEJOR Y CON CLARIDAD MANIFIESTA CON LOS EJEMPLOS.
VAYAMOS, PUES, PASO A PASO.
Los decoradores constituyen por sí mismas unas herramientas muy eficaces para discurrir por la senda del ahorro y de la reutilización del código que tanto gusta a Python, dado que por una parte nos exime de tener que escribir más código, y por otra, aprovechando que esa misma función puede ser aplicada en otras funciones diferentes que podamos definir a lo largo de nuestro código, convertirla en un decorador y, ya como tales, invocarlas en las funciones que queramos sin tener que repetir el código en cada ocasión.
Para hacernos una idea de su potencia vamos a suponer que acabamos de desarrollar un programa que suma unas treinta funciones definidas con mucho sudor por nosotros. Mientras repasamos el código se nos ocurre que puede haber alguna funcionalidad interesante que no hemos tenido en cuenta en el momento de la codificación (¡Ostras!, ¡Qué pena! Suele pasar muy a menudo en el mundo de la programación real y nos sucederá a nosotros, como programadores, innumerables veces. De hecho, muchas de estas "ocurrencias", cuando son de cierta envergadura, justifican la aparición de versiones de distintos programas, como las del propio Python, sin ir más lejos. Así, para modificaciones menores, "de andar por casa" como quien dice, los lenguajes de programación crean mecanismos y herramientas, como los mismos decoradores que estamos tratando aquí para ayudarnos como sufridos programadores a no re-escribir código una y otra vez). Entendemos que podría beneficiar a la mitad de nuestro código, a unas quince funciones. Pero con el recurso a los decoradores podríamos tan sólo definir una función decorator que contenga el script que queremos añadir, e incrustarlo de una sola vez, de un plumazo, en todas y cada una de las quince funciones que seleccionamos. Suena bien, ¿verdad?
BEJEQUES COLONIZANDO PAREDES DE PIEDRA MAMPUESTA Y TEJADOS ANARILLADOS DE MUSGO SECO. CUALQUIER PARTE MÁS O MENOS HÚMEDA DE LA GEOGRAFÍA DE TENERIFE. |
Vamos con una analogía, a ver si conseguimos fijar mejor la idea. Imaginemos que nos compramos un robot de cocina, una suerte de Termomix, sencillita, vamos, que es capaz de hervir, guisar y freír alimentos y ya está.
Sin embargo, como somos así de curiosos, navegando por la web, descubrimos un tutorial en que se nos enseña cómo manipular nuestro robot para que, además de hervir, freír y guisar, exprima fruta y trocee verdura. Esto pasa por adquirir un pequeño complemento, un artilugio mecánico que se acopla a nuestro robot con un simple tornillo.
Pues nada. manos a la obra, que además de curiosos nos tenemos por amañados, y... ¡voilà! Ahora nuestro sencillo robot de cocina original es todo un maquinón digno del chef más reputado.
Nuestro sencillo robot original es la función principal que hemos diseñado (definido) que es capaz de hacer tres cosas (métodos): hervir, freír y guisar. Al robot de cocina le añadimos posteriormente el complemento que hemos adquirido (decorador) mediante el tornillo de marras (la sintaxis) y, gracias a él, sin tener que desmontar y volver a montar el robot pieza a pieza (reescribir código), hemos mejorado y ampliado las funcionalidades de nuestro robot (función principal) añadiendo las opciones (métodos nuevos) de exprimir y trocear.
Con todo lo que sabemos hasta ahora ya podemos mostrar un diagrama/esquema con la estructura de un decorador:
Sabiendo esto ya sólo nos resta por conocer el modo en que debemos llamar a una función decorator o decorador. Para hacerlo tan sólo necesitamos usar la sintaxis @función_decoradora, situada justo por encima del nombre de la función que queremos decorar para ampliar sus funcionalidades.
Como vemos, cuando llamamos al decorador no tenemos que pasarle argumento alguno: el decorador asume de manera implícita que su parámetro funcion_parametro se corresponde con la función definida que colocamos justamente debajo. Así, la funcion_parametro de @decoradora1 es animal() y la funcion_parametro de @decoradora2 es pot().
Ya vamos viendo la luz al final del túnel, ¿verdad?
Aunque es posible apilar llamadas a decoradores, debemos tener precaución con los resultados que esperamos obtener, ya que el efecto decorador (la adición de nuevas funcionalidades) es englobante.
Ya vamos viendo la luz al final del túnel, ¿verdad?
Aunque es posible apilar llamadas a decoradores, debemos tener precaución con los resultados que esperamos obtener, ya que el efecto decorador (la adición de nuevas funcionalidades) es englobante.
Con este esquema, se empieza a ejecutar @decorator1. Cuando llega a la función_parámetro, esto es, funcion(), observa que a ésta la antecede @decorator2, así que ejecuta el código inserto en @decorator2 e inmediatamente después, el de la propia función_parámetro , y termina con la ejecución del resto del código de @decorator1.
Lo vemos en el siguiente ejemplo:
LA INCLUSIÓN DEL CARÁCTER @ DELANTE DEL NOMBRE DEL decorador TIENE QUE VER CON EL TAMAÑO (ENTIÉNDASE, CANTIDAD DE CÓDIGO) DEL PROPIO decorador, O DE LA "DISTANCIA" QUE MEDIE ENTRE SU DEFINICIÓN Y SU LLAMADA ULTERIOR PARA ACTUAR SOBRE LA función_parámetro DE NUESTRA ELECCIÓN EN UN PROGRAMA DE CIENTOS O MILES DE LÍNEAS.
ASÍ, CON UN CARÁCTER TAN LLAMATIVO COMO EL DE LA ARROBA, @, PODEMOS LOCALIZAR E IDENTIFICAR RÁPIDAMENTE QUÉ decoradores Y CUÁNTOS DE ÉSTOS ESTÁN EJECUTÁNDOSE SOBRE UNA función_parámetro.
TIENE SU COSILLA, ¿NO?
Sin embargo, los decoradores no funcionan como se espera de ellos según cómo se interrelacionen con los elementos propios del código de las funciones_parámetro. Esto, como podemos deducir por lo que ya sabemos, puede ocurrir porque las variables y expresiones declaradas FUERA del ámbito de una función dada no pueden acceder a ésta de manera directa. Recordemos: variables locales versus variables globales. Veámoslo:
FLORACIÓN ESTACIONAL DEL CARDO CANARIO EN EL MONTEVERDE DE ERJOS, NORTE DE TENERIFE. |
Aún así, es posible revertir esta situación si asignamos a nuestra variable mult la cláusula global, que es el método que empleamos para introducir en el ámbito de una función definida por el usuario una variable declarada FUERA de ella. Lo vemos a continuación:
Acabamos de comprobar que disponemos de un instrumento válido para tomar elementos externos e insertarlos en el ámbito de una función_parámetro desde el propio decorador. Cierto. Pero también que, a parte de resultar un procedimiento un punto farragoso, puede aumentar en exceso el tamaño de la función decoradora, y aún limitar su operatividad.
Por este motivo muchos programadores optan por recurrir los decoradores para COMPLEMENTAR (más que propiamente agregar o añadir elementos o código) una función de una manera, podemos decir, transversal, en "paralelo" a la funcionalidad específica de la función, sin interferir en modo alguno con el código escrito en el ámbito de la función. éste es el sentido de la complementariedad que mencionábamos antes: mientras la función propiamente dicha ejecuta su código, el decorador aporta complementos, envolturas, marcos que se engarcen con la función en sí., bien "disponiendo el campo" para que la función pueda operar (por ejemplo, definimos una función que trabaja con una tabla de datos y la función decoradora se encarga de abrir y cerrar la conexión a la base de datos; o definimos una función que permite añadir contenido a un documento de texto y la función decoradora abre y cierra el acceso al fichero); bien efectuando validaciones previas de permiso de acceso, loggins, sobre la idoneidad o no de la solicitud, o de otros elementos que puedan afectar a la ejecución correcta de la función:parámetro; etc.).
ACUMULACIÓN DE HOJARASCA EN EL RIBAZO DE UNA TORRENTERA TRAS LAS LLUVIAS, EN LA DESEMBOCADURA DEL BARRANCO DE IBOIBO, OESTE DE TENERIFE. |
DECORADORES CON PARÁMETROS:
Se trata de que las funciones_parámetro (recordemos, aquéllas sobre las que actúa la función decoradora) puedan contar con una zona de parámetros habilitada para que el usuario pueda pasar información (argumentos). Podríamos esta hablando de una función tipo como ésta:
Para obtener una decoración exitosa debemos situarnos en el cuerpo de la función decoradora, y centrar nuestra atención en la función interna. Aquí, en su zona de parámetros implementaremos el comodín *args, lo que le dice a la función que puede recibir un número desconocido o indeterminado de argumentos obligatorios (en cada ocasión, el que coincida con los parámetros originales de la función que queremos decorar).
Y sin salirnos del cuerpo de la función decoradora, localizar a su vez la función_parámetro y añadirle el mismo comodín *args.
Podemos incrementar las funcionalidades de nuestra función_parámetro y la función decoradora continuar ejecutándose espléndidamente. Podemos añadir argumentos de palabra/clave (keywordos) en la zona de parámetros de nuestra función con el comodín **kwargs que, recordemos, acepta variables con asignación(color = "rojo", altura = 0.9, etc.).
Recuperamos un esquema anterior para refrescar memoria:
Vemos un ejemplo:
Concluimos con unos ejemplos tomados del website BOGOTOGO, de K. Hong, cuya lectura recomendamos de manera entusiasta. Tengamos en cuenta que en dichos ejemplos, el argumento f se corresponde a la función_parámetro de nuestros ejemplos.
EJEMPLO 1:
EJEMPLO 2:
EJEMPLO 3:
ESCOLLOS EN LA PLAYA DEL BOLLULLO, NORTE DE TENERIFE. |
No hay comentarios:
Publicar un comentario