Diferències
Ací es mostren les diferències entre la revisió seleccionada i la versió actual de la pàgina.
Ambdós costats versió prèvia Revisió prèvia | |||
info:cursos:netacad:python:pe2m4:generadores [06/07/2022 08:01] – mate | info:cursos:netacad:python:pe2m4:generadores [07/07/2022 07:12] (actual) – [Listas por comprensión frente a generadores] mate | ||
---|---|---|---|
Línia 451: | Línia 451: | ||
En el segundo bucle, no hay ninguna lista, solo hay valores subsecuentes producidos por el generador, uno por uno. | En el segundo bucle, no hay ninguna lista, solo hay valores subsecuentes producidos por el generador, uno por uno. | ||
- | Realiza tus propios experimentos. | + | == La función lambda |
+ | La función lambda es un concepto tomado de las matemáticas, | ||
+ | |||
+ | Los matemáticos usan el //Cálculo Lambda// en sistemas formales conectados con: la lógica, la recursividad o la demostrabilidad de teoremas. Los programadores usan la función //lambda// para simplificar el código, hacerlo más claro y fácil de entender. | ||
+ | |||
+ | Una función lambda es una función sin nombre (también puedes llamarla **una función anónima**). Por supuesto, tal afirmación plantea inmediatamente la pregunta: ¿cómo se usa algo que no se puede identificar? | ||
+ | |||
+ | Afortunadamente, | ||
+ | |||
+ | La declaración de la función lambda no se parece a una declaración de función normal; compruébalo tu mismo: | ||
+ | < | ||
+ | lambda parámetros: | ||
+ | </ | ||
+ | |||
+ | Tal cláusula devuelve el valor de la expresión al tomar en cuenta el valor del argumento lambda actual. | ||
+ | |||
+ | Como de costumbre, un ejemplo será útil. Nuestro ejemplo usa tres funciones lambda, pero con nombres. Analízalo cuidadosamente: | ||
+ | <code python> | ||
+ | two = lambda: 2 | ||
+ | sqr = lambda x: x * x | ||
+ | pwr = lambda x, y: x ** y | ||
+ | |||
+ | for a in range(-2, 3): | ||
+ | print(sqr(a), | ||
+ | print(pwr(a, | ||
+ | </ | ||
+ | |||
+ | Vamos a analizarlo: | ||
+ | |||
+ | La primer //lambda// es una función anónima **sin parámetros** que siempre devuelve un 2. Como se ha a**signado a una variable llamada dos**, podemos decir que la función ya no es anónima, y se puede usar su nombre para invocarla. | ||
+ | |||
+ | La segunda es una **función anónima de un parámetro** que devuelve el valor de su argumento al cuadrado. Se ha nombrado también como tal. | ||
+ | |||
+ | La tercer lambda **toma dos parámetros** y devuelve el valor del primero elevado al segundo. El nombre de la variable que lleva la lambda habla por si mismo. No se utiliza pow para evitar confusiones con la función incorporada del mismo nombre y el mismo propósito. | ||
+ | |||
+ | El programa produce el siguiente resultado: | ||
+ | < | ||
+ | 4 4 | ||
+ | 1 1 | ||
+ | 0 0 | ||
+ | 1 1 | ||
+ | 4 4 | ||
+ | </ | ||
+ | |||
+ | Este ejemplo es lo suficientemente claro como para mostrar como se declaran las funciones lambda y cómo se comportan, pero no dice nada acerca de por que son necesarias y para qué se usan, ya que se pueden reemplazar con funciones de Python de rutina. | ||
+ | |||
+ | === ¿Cómo usar lambdas y para qué? | ||
+ | La parte más interesante de usar lambdas aparece cuando puedes usarlas en su forma pura: **como partes anónimas de código destinadas a evaluar un resultado**. | ||
+ | |||
+ | Imagina que necesitamos una función (la nombraremos // | ||
+ | |||
+ | Queremos que // | ||
+ | |||
+ | <code python> | ||
+ | def print_function(args, | ||
+ | for x in args: | ||
+ | print(' | ||
+ | |||
+ | |||
+ | def poly(x): | ||
+ | return 2 * x**2 - 4 * x + 2 | ||
+ | |||
+ | |||
+ | print_function([x for x in range(-2, 3)], poly) | ||
+ | |||
+ | </ | ||
+ | |||
+ | Analicémoslo. La función '' | ||
+ | * El primero, una lista de argumentos para los que queremos imprimir los resultados. | ||
+ | * El segundo, una función que debe invocarse tantas veces como el número de valores que se recopilan dentro del primer parámetro. | ||
+ | |||
+ | Nota: También hemos definido una función llamada '' | ||
+ | |||
+ | < | ||
+ | |||
+ | El nombre de la función se pasa a '' | ||
+ | |||
+ | El código imprime las siguientes líneas: | ||
+ | < | ||
+ | f(-2)=18 | ||
+ | f(-1)=8 | ||
+ | f(0)=2 | ||
+ | f(1)=0 | ||
+ | f(2)=2 | ||
+ | </ | ||
+ | |||
+ | ¿Podemos evitar definir la función '' | ||
+ | |||
+ | |||
+ | Mira el ejemplo de abajo. ¿Puedes ver la diferencia? | ||
+ | <code python> | ||
+ | def print_function(args, | ||
+ | for x in args: | ||
+ | print(' | ||
+ | |||
+ | print_function([x for x in range(-2, 3)], lambda x: 2 * x**2 - 4 * x + 2) | ||
+ | </ | ||
+ | |||
+ | La función '' | ||
+ | |||
+ | <code python> | ||
+ | |||
+ | El código se ha vuelto más corto, más claro y más legible. | ||
+ | |||
+ | Permítenos mostrarte otro lugar donde las lambdas pueden ser útiles. Comenzaremos con una descripción de '' | ||
+ | |||
+ | === Lambdas y la función map() | ||
+ | En el más simple de todos los casos posibles, la función '' | ||
+ | <code python> | ||
+ | map(function, | ||
+ | </ | ||
+ | |||
+ | Toma dos argumentos: | ||
+ | * Una función. | ||
+ | * Una lista. | ||
+ | |||
+ | La descripción anterior está extremadamente simplificada, | ||
+ | * El segundo argumento '' | ||
+ | * '' | ||
+ | |||
+ | La función '' | ||
+ | |||
+ | Puedes usar el iterador resultante en un bucle o convertirlo en una lista usando la función '' | ||
+ | |||
+ | ¿Puedes ver un papel para una lambda aquí? | ||
+ | |||
+ | <code python> | ||
+ | list_1 = [x for x in range(5)] | ||
+ | list_2 = list(map(lambda x: 2 ** x, list_1)) | ||
+ | print(list_2) | ||
+ | |||
+ | for x in map(lambda x: x * x, list_2): | ||
+ | print(x, end=' ') | ||
+ | print() | ||
+ | |||
+ | </ | ||
+ | Hemos usado dos lambdas en él. | ||
+ | |||
+ | Esta es la explicación: | ||
+ | * Se construye la list_1 con valores del 0 al 4. | ||
+ | * Después, se utiliza '' | ||
+ | * list_2 se imprime. | ||
+ | * En el siguiente paso, se usa nuevamente la función '' | ||
+ | * | ||
+ | Intenta imaginar el mismo código sin lambdas. ¿Sería mejor? Es improbable. | ||
+ | |||
+ | === Lambdas y la función filter() | ||
+ | Otra función de Python que se puede embellecer significativamente mediante la aplicación de una lambda es '' | ||
+ | |||
+ | Espera el mismo tipo de argumentos que '' | ||
+ | |||
+ | Los elementos que devuelven True de la función **pasan el filtro**, los otros son rechazados. | ||
+ | |||
+ | <code python> | ||
+ | from random import seed, randint | ||
+ | |||
+ | seed() | ||
+ | data = [randint(-10, | ||
+ | filtered = list(filter(lambda x: x > 0 and x % 2 == 0, data)) | ||
+ | |||
+ | print(data) | ||
+ | print(filtered) | ||
+ | </ | ||
+ | |||
+ | Nota: hemos hecho uso del módulo //random// para inicializar el generador de números aleatorios (que no debe confundirse con los generadores de los que acabamos de hablar) con la función '' | ||
+ | |||
+ | Luego se filtra la lista y solo se aceptan los números que son pares y mayores que cero. | ||
+ | |||
+ | Por supuesto, no es probable que recibas los mismos resultados, pero así es como se veían nuestros resultados: | ||
+ | < | ||
+ | [6, 3, 3, 2, -7] | ||
+ | [6, 2] | ||
+ | </ | ||
+ | |||
+ | == Una breve explicación de cierres | ||
+ | Comencemos con una definición: | ||
+ | |||
+ | Analicemos un ejemplo simple: | ||
+ | <code python> | ||
+ | def outer(par): | ||
+ | loc = par | ||
+ | |||
+ | |||
+ | var = 1 | ||
+ | outer(var) | ||
+ | |||
+ | print(var) | ||
+ | print(loc) | ||
+ | </ | ||
+ | |||
+ | El ejemplo es obviamente erróneo. | ||
+ | |||
+ | Las dos últimas líneas provocarán una excepción // | ||
+ | |||
+ | <code python> | ||
+ | def outer(par): | ||
+ | loc = par | ||
+ | |||
+ | def inner(): | ||
+ | return loc | ||
+ | return inner | ||
+ | |||
+ | |||
+ | var = 1 | ||
+ | fun = outer(var) | ||
+ | print(fun()) | ||
+ | </ | ||
+ | |||
+ | Hay un elemento completamente nuevo - una función (llamada //inner//) dentro de otra función (llamada // | ||
+ | |||
+ | ¿Cómo funciona? Como cualquier otra función excepto por el hecho de que '' | ||
+ | |||
+ | Observa cuidadosamente: | ||
+ | * La función '' | ||
+ | * La función '' | ||
+ | |||
+ | En efecto, el código es totalmente válido y genera: | ||
+ | < | ||
+ | |||
+ | La función devuelta durante la invocación de '' | ||
+ | |||
+ | **Un cierre se debe invocar exactamente de la misma manera en que se ha declarado**. | ||
+ | |||
+ | En el ejemplo anterior (vea el código a continuación): | ||
+ | <code python> | ||
+ | def outer(par): | ||
+ | loc = par | ||
+ | |||
+ | def inner(): | ||
+ | return loc | ||
+ | return inner | ||
+ | |||
+ | |||
+ | var = 1 | ||
+ | fun = outer(var) | ||
+ | print(fun()) | ||
+ | </ | ||
+ | |||
+ | La función '' | ||
+ | |||
+ | <code python> | ||
+ | def make_closure(par): | ||
+ | loc = par | ||
+ | |||
+ | def power(p): | ||
+ | return p ** loc | ||
+ | return power | ||
+ | |||
+ | |||
+ | fsqr = make_closure(2) | ||
+ | fcub = make_closure(3) | ||
+ | |||
+ | for i in range(5): | ||
+ | print(i, fsqr(i), fcub(i)) | ||
+ | |||
+ | </ | ||
+ | Es totalmente posible **declarar un cierre equipado con un número arbitrario de parámetros**, | ||
+ | |||
+ | Esto significa que el cierre no solo utiliza el ambiente congelado, sino que también puede **modificar su comportamiento utilizando valores tomados del exterior**. | ||
+ | |||
+ | Este ejemplo muestra una circunstancia más interesante: | ||
+ | * El primer cierre obtenido de make_closure() define una herramienta que eleva al cuadrado su argumento. | ||
+ | * El segundo está diseñado para elevar el argumento al cubo. | ||
+ | |||
+ | Es por eso que el código produce el siguiente resultado: | ||
+ | < | ||
+ | 0 0 0 | ||
+ | 1 1 1 | ||
+ | 2 4 8 | ||
+ | 3 9 27 | ||
+ | 4 16 64 | ||
+ | </ | ||
+ | |||
+ | == Puntos Clave | ||
+ | 1. Un **iterator** es un objeto de una clase que proporciona al menos **dos** métodos (sin contar el constructor): | ||
+ | * '' | ||
+ | * '' | ||
+ | |||
+ | 2. La sentencia //yield// solo puede ser utilizada dentro de funciones. La sentencia //yield// suspende la ejecución de la función y hace que la función regrese el argumento de yield como resultado. Esta función no puede invocarse de forma regular, su único propósito es ser utilizada como un **generador** (es decir, en un contexto que requiera una serie de valores, como un bucle for). | ||
+ | |||
+ | 3. Una **expresión condicional** es una expresión construida usando el operador if-else. Por ejemplo: | ||
+ | <code python> | ||
+ | print(True if 0 >= 0 else False) | ||
+ | </ | ||
+ | |||
+ | Da como salida: True. | ||
+ | |||
+ | 4. Una **lista por comprensión** se convierte en un **generador** cuando se emplea dentro de **paréntesis** (usado entre corchetes, produce una lista regular). Por ejemplo: | ||
+ | <code python> | ||
+ | for x in (el * 2 for el in range(5)): | ||
+ | print(x) | ||
+ | </ | ||
+ | Da como salida: 02468. | ||
+ | |||
+ | |||
+ | 5. Una **función lambda** es una herramienta para crear **funciones anónimas**. Por ejemplo: | ||
+ | <code python> | ||
+ | def foo(x, f): | ||
+ | return f(x) | ||
+ | |||
+ | print(foo(9, | ||
+ | </ | ||
+ | |||
+ | Da como salida: 3.0. | ||
+ | |||
+ | |||
+ | 6. La función '' | ||
+ | <code python> | ||
+ | short_list = [' | ||
+ | new_list = list(map(lambda s: s.title(), short_list)) | ||
+ | print(new_list) | ||
+ | </ | ||
+ | |||
+ | Da como salida: [' | ||
+ | |||
+ | 7. La función '' | ||
+ | <code python> | ||
+ | short_list = [1, " | ||
+ | new_list = list(filter(lambda s: isinstance(s, | ||
+ | print(new_list) | ||
+ | </ | ||
+ | |||
+ | Da como salida: [' | ||
+ | |||
+ | 8. Un cierre es una técnica que permite almacenar valores a pesar de que el contexto en el que han sido creados no existe más. Por ejemplo: | ||
+ | <code python> | ||
+ | def tag(tg): | ||
+ | tg2 = tg | ||
+ | tg2 = tg[0] + '/' | ||
+ | |||
+ | def inner(str): | ||
+ | return tg + str + tg2 | ||
+ | return inner | ||
+ | |||
+ | b_tag = tag('< | ||
+ | print(b_tag(' | ||
+ | </ | ||
+ | |||
+ | Da como salida: < | ||
+ | |||
- | https:// | ||