viernes, 11 de agosto de 2017

T2. LAS FUNCIONES LAMBDA: ELOGIO DE LA SENCILLEZ.

PINAR Y MONTEVERDE EN LAS LADERAS ALTAS DE LOS SILOS, NOROESTE DE TENERIFE, CON LA ISLA DE LA PALMA ASOMANDO AL FONDO, POR ENCIMA DEL MAR DE NUBES.

      Las funciones lambda constituyen un tipo especial de función dentro del género de funciones predefinidas en Python. A parte de una sintaxis exclusiva, las funciones lambda  se caracterizan por una función anónima cuando se le asigna una variable concreta.
Dichas funciones ejecutan una expresión y devuelve el resultado de la misma. Pueden llevar o no parámetros, igual que las funciones def que ya conocemos, y cuando los lleva suelen ser casi siempre nombres de variables (recordemos: referencias a espacios de memoria que almacenan objetos. Y prácticamente todo, incluidas las propias funciones, son objetos en Python:  variables, conjuntos, tuplas, números, bytes, listas, etc.) separados por comas: señalemos que en el momento en que separamos por comas unas variables de otras, éstas se convierten automáticamente en "argumentos posicionales", lo que nos obliga a respetar su orden para cumplir con aquéllo de "cada oveja con su pareja", "cada variable con su dato".
Se les puede aplicar toda la estructura de argumentos que soportan las funciones def y que hemos visto con anterioridad dotándola de una alta operatividad. Sin embargo, cuenta con restricciones importantes como que la expresión no puede contener bucles (for/in y while) aunque sí permite el uso del condicional if. Tampoco utiliza la sentencia return que, recordemos, constituye el pie de las funciones definidas por el usuario.
Dicho esto, veamos ahora la estructura sintáctica de las funciones lambda:


Las funciones lambda, más que una sentencia, constituye en sí misma una expresión, casi un simple algoritmo como cualquier fórmula física que se nos ocurra, una velocidad = espacio/tiempo, para entendernos, donde anteponemos la palabra reservada lambda, coma para que se note, los valores (o variables que almacenan esos mismos valores), dos puntos (no hay indentado) y la expresión (algoritmo) a ejecutar.
La siguiente tabla nos puede resultar particularmente esclarecedora:




La utilidad de las funciones lambda estriba en que nos permite definir una función dentro del propio código, es decir, de manera implícita al mismo (inline) sin más aditamentos sintácticos, por lo que en comparación con las funciones def que ya conocemos, desaparecen, desde el punto de vista de la estructura sintáctica, el cuerpo y el pie de la función (no existe sentencia return que valga) y, en consecuencia, la indentación o sangrado. Por esta razón, nos vienen de perilla para resolver de manera ágil cierto tipo de situaciones, por ejemplo, cuando elaboramos una lista sobre la que queremos ejecutar distintas operaciones y decidirnos por una u otra según nuestra conveniencia; también cuando nos toca efectuar una serie de cálculos concretos con variables similares durante la elaboración de un programa; cuando queremos crear un diccionario por defecto, etc...
Vayamos viendo ejemplos: supongamos que se nos presenta la opción de diseñar dos funciones def para obtener dos resultados con los que operar más tarde y obtener el volumen de una pirámide. Para ello, necesitamos conocer el área de la base (cuadrada) y el área de una de las cuatro caras de la pirámide (triángulo isósceles). Como sólo nos interesa el resultado, podríamos proceder de la siguiente forma:



En 1. generamos una lista de variable homónima (lista) con sendas listas (sublistas) anidadas a nivel 1 en su interior. Cada una de ellas contiene una función lambda con su estructura prototipo: la palabra reservada lambda; el parámetro correspondiente; los dos puntos y la expresión correspondiente a lo que queramos calcular, en el primer caso, lado por lado para hallar el área de la base; y en el segundo, lado por altura entre dos para hacer lo propio con el triángulo.
A continuación, en 2. y 3. creamos dos nuevas variables x e y, para llamar a cada una de estas listas anidadas por su índice, [0] para la primera y [1] para la segunda.
En 4., mediante la asignación abreviada, establecemos los valores correspondientes a cada parámetro: lado y altura.
En 5. y 6. construimos  un bucle for/in, por supuesto fuera de la expresión lambda que, como ya sabemos, igual que sucede con el bucle while, no los admite, para cada una de las funciones lambda. Notemos que a la hora de llamar a la función print(), junta a la palabra  'item' que señala a los elementos de la función lambda pasamos el argumento correspondiente entre paréntesis.
Obviamente, puestos a poner ejemplos, aquí mostramos un par más, en esta ocasión, todo un canto a la sencillez. La mayor parte de las veces, cuando estemos programando y se nos ocurra recurrir a una función lambda para solventar una situación, será para introducir códigos tan sencillos como éstos:


PAREIDOLIA DE UN ROSTRO EN LA PARED EN LOS TAJOS EN EL MONTEVERDE DE VALLE BROSQUE, PARTE ALTA DEL BARRANCO, SUR DE ANAGA.
Las funciones lambda pueden ser empleadas sin ningún tipo de objeción cuando se las requiere desde otra función, incluso desde una función definida por el usuario tratándolas como simples expresiones. Veamos un par de casos:



Como vemos, todo resulta muy sencillo. Y aún más asociando directamente la expresión lambda a la sentencia return por aquello de la elegancia y el ahorro de código.

   
      Una función lambda actúa u opera sobre un único elemento, x, en base a la sintaxis que ya conocemos: lambda x: expresión_x.¿Guau?..., digo...¿Vale?
Para que una función lambda recorra los ítems de un iterable (x es ahora cada uno de los ítems de la secuencia), nos viene bien echarle la pata..., esto..la mano a la función built-in de Python map(), que le confiere una capacidad o potencial iterador sobre una secuencia que pasamos como segundo argumento de la función:

                                       map(función lambda, secuencia)


      

      LA FUNCIÓN map():

   
      Podemos recurrir a la función integrada map() para pasar como parámetros la función lambda a otra función de corte más convencional. La función map(), como podemos ver en el esquema inferior, recibe dos parámetros: el primero, 'func', es una función, en nuestro caso, la función lambda con toda su sintaxis; el segundo, la variable que almacena todos los elementos (items) a los que se le va a aplicar la función, (*iterables). Por ello, normalmente la aplicaremos sobre secuencias de datos, como listas, por ejemplo.
Hemos enmarcado en rojo la literatura que realmente nos interesa de toda la información que nos proporciona el recurso a la función de ayuda help() sobre la clase map (v. type(), al final) y que, traducido, viene a decir lo siguiente: construye un iterador que computa la función lambda aplicándolo a los argumentos de cada iterable (lista, conjunto,...). Concluye cuando se ha iterado por todos los ítems del iterable.



Veamos un ejemplo de su funcionamiento:



En 1. construimos un iterable, en este caso, una lista asociada a la variable 'decenas' que contiene 5 ítemes, todos ellos números enteros (int). En 2. declaramos una variable para poder invocarla más adelante, a la que llamamos en el colmo de la originalidad, lista_decenas y a la que le asignamos la función map(). Como ya sabemos, la función map() llevará dos argumentos: una función lambda, con su sintaxis propietaria y, tras la coma de rigor que separa los argumentos, el nombre de la variable que almacena el iterable que construimos en 1., 'decenas',que es el la lista que contiene los distintos ítems sobre los que queremos aplicar la función lambda. Ya en 3. instauramos el consiguiente bucle for/in para que actúe sobre la nueva variable que creamos justo en la línea de código anterior, lista_decenas. Finalmente, solicitamos un print() para comprobar que todo ha ido bien.
No parece que sea muy complicado, ¿verdad?
Mostraremos un par de ejemplos más. En ambos casos, el iterable será una tupla.



Para finalizar, en el ejemplo que sigue cambiamos una función lambda por una función definida por el usuario. Comprobamos su operatividad, aunque su utilidad es muy limitada.



   Algunas cosillas nos quedan por saber sobre la función map() de Python. No tiene que ver con la función lambda que da título a este apartado, por lo que hemos decidido dedicarle una entrada propia, entre otras funciones conectadas.

EL RARO TAJINASTE AZUL EN PLENA FLORACIÓN. PARQUE NACIONAL DE LAS CAÑADAS DE EL TEIDE.

      LA INCLUSIÓN DEL CONDICIONAL if:

      
      No es muy habitual, aunque sí resulta factible, incluir una condición dentro de una función lambda, aunque su devolución no destacará por su finura, precisamente.




      EL MÉTODO sort() DE LAS LISTAS Y LA FUNCIÓN lambda. LA FUNCIÓN sorted():

      
      Las funciones lambda se suelen emplear, como expresiones que son al fin y al cabo, como funciones clave para la función integrada sorted() e, incluso, para el método sort() de las listas. Veamos un ejemplo: imaginemos que tenemos una lista, que llamaremos citysport, con los nombres y dorsales de varios jugadores del equipo de fútbol "City Sport", almacenados dentro de un tipo de dato tupla (2-tupla):

                                                   citysport = [(4, "Raúl"), (1, "Javier"), (7, "Gonzalo"), (2, "Saúl")]

Esta lista citysport puede ordenarse por su propio método:


Tal y como podemos ver se produce un ordenamiento de acuerdo al orden numérico, de menor a mayor, y no por el orden alfabético.
La función sorted() puede, por su parte, ella solita, proporcionarnos una función clave para cambiar el orden. Para ello podríamos definir una función que ejerza como tal, pero la solución más común pasa por recurrir a una función lambda:



El procedimiento es harto simple: como vemos en 1., llamamos al método sort() de las listas y le pasamos como argumento una variable que llamamos 'key' y a la que le asignamos toda una función lambda hecha y derecha con su sintaxis prototipo. Se hacen imprescindibles los paréntesis cuando la expresión sea una tupla, por lo que insertamos entre ellos las llamadas a los índices, separados por comas, al solicitar la lista con la nueva ordenación, se nos mostrará un resultado similar al ejemplo anterior. Si queremos ordenar los ítems de acuerdo al alfabeto nos basta con tan sólo invertir el orden de los índices, tal y como hemos hecho en 2. y... ¡Conseguido!
Podemos, incluso, por qué no, con tuplas que contengan todos los elementos que queramos. Ponemos un ejemplo:



También, si podemos trabajar con índices, podemos hacerlo igualmente con rebanadas (slices), tan poco que nos acordamos de ellas y tanto que nos pueden resolver. De muestra, un botón:



Terminamos haciendo una referencia a la función integrada sorted() de Python. Esta función realiza la misma función, valga la redundancia, que el método sort() de las listas, pero sin necesidad de invocar al método, es decir, realiza el ordenamiento de manera automática con sólo pasar la secuencia que queramos ordenar como argumento de la misma. Aquí tenemos un ejemplo:




Teniendo en cuenta que la función lambda es una función de nivel superior, esto es, que lleva como argumento una función, podemos construir un código alternativo a una cascada de condicionales if/elif/else, muy elegante, por cierto, y con un punto de complejidad, del siguiente modo:


Así, a través del método get() de los diccionarios, podemos seleccionar una operación aritmética entre las cuatro posibles y pasarle sus respectivos valores.

TABAIBAS Y ALMENDROS EN EL PAISAJE DE VALLE DE SANTIAGO, OESTE DE TENERIFE.

Rematamos la faena proponiendo el visionado a continuación, en la siguiente entrada, de las funciones recursivas: T2. LAS FUNCIONES RECURSIVAS: PROBANDO, PROBANDO...


No hay comentarios:

Publicar un comentario