Diferències

Ací es mostren les diferències entre la revisió seleccionada i la versió actual de la pàgina.

Enllaç a la visualització de la comparació

Ambdós costats versió prèvia Revisió prèvia
info:cursos:netacad:python:pe2m4:generadores [06/07/2022 08:01] mateinfo: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, más específicamente, de una parte llamada el //Cálculo Lambda//, pero estos dos fenómenos no son iguales. 
 + 
 +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, no es un problema, ya que se puede mandar llamar dicha función si realmente se necesita, pero, en muchos casos la función //lambda// puede existir y funcionar mientras permanece completamente de incógnito. 
 + 
 +La declaración de la función lambda no se parece a una declaración de función normal; compruébalo tu mismo: 
 +<code> 
 +lambda parámetros: expresión 
 +</code> 
 + 
 +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), end=" ") 
 +    print(pwr(a, two())) 
 +</code> 
 + 
 +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: 
 +<code> 
 +4 4 
 +1 1 
 +0 0 
 +1 1 
 +4 4 
 +</code> 
 + 
 +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 //print_function//) que imprime los valores de una (otra) función dada para un conjunto de argumentos seleccionados. 
 + 
 +Queremos que //print_function// sea universal, debería aceptar un conjunto de argumentos incluidos en una lista y una función a ser evaluada, ambos como argumentos; no queremos codificar nada. 
 + 
 +<code python> 
 +def print_function(args, fun): 
 +    for x in args: 
 +        print('f(', x,')=', fun(x), sep=''
 + 
 + 
 +def poly(x): 
 +    return 2 * x**2 - 4 * x + 2 
 + 
 + 
 +print_function([x for x in range(-2, 3)], poly) 
 + 
 +</code> 
 + 
 +Analicémoslo. La función ''print_function()'' toma dos parámetros: 
 +  * 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 ''poly()'', esta es la función cuyos valores vamos a imprimir. El cálculo que realiza la función no es muy sofisticado: es el polinomio (de ahí su nombre) de la forma: 
 + 
 +<code>f(x) = 2x2 - 4x + 2</code> 
 + 
 +El nombre de la función se pasa a ''print_function()'' junto con un conjunto de cinco argumentos diferentes: el conjunto está construido con una cláusula de comprensión de la lista. 
 + 
 +El código imprime las siguientes líneas: 
 +<code> 
 +f(-2)=18 
 +f(-1)=8 
 +f(0)=2 
 +f(1)=0 
 +f(2)=2 
 +</code> 
 + 
 +¿Podemos evitar definir la función ''poly()'', ya que no la vamos a usar más de una vez? Si, podemos: este es el beneficio que puede aportar una función lambda. 
 + 
 + 
 +Mira el ejemplo de abajo. ¿Puedes ver la diferencia? 
 +<code python> 
 +def print_function(args, fun): 
 +    for x in args: 
 +        print('f(', x,')=', fun(x), sep=''
 + 
 +print_function([x for x in range(-2, 3)], lambda x: 2 * x**2 - 4 * x + 2) 
 +</code> 
 + 
 +La función ''print_function()'' se ha mantenido exactamente igual, pero no hay una función ''poly()''. Ya no la necesitamos, ya que el polinomio ahora está directamente dentro de la invocación de la función ''print_function()'' en forma de una lambda definida de la siguiente manera: 
 + 
 +<code python>lambda x: 2 * x**2 - 4 * x + 2</code> 
 + 
 +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 ''map()'', una función integrada de Python. Su nombre no es demasiado descriptivo, su idea es simple y la función en sí es muy utilizable. 
 + 
 +=== Lambdas y la función map() 
 +En el más simple de todos los casos posibles, la función ''map()'': 
 +<code python> 
 +map(function, list) 
 +</code> 
 + 
 +Toma dos argumentos: 
 +  * Una función. 
 +  * Una lista. 
 + 
 +La descripción anterior está extremadamente simplificada, ya que: 
 +  * El segundo argumento ''map()'' puede ser cualquier entidad que se pueda iterar (por ejemplo, una tupla o un generador). 
 +  * ''map()'' puede aceptar más de dos argumentos. 
 + 
 +La función ''map()'' **aplica la función pasada por su primer argumento a todos los elementos de su segundo argumento y devuelve un iterador que entrega todos los resultados de funciones subsequentes**. 
 + 
 +Puedes usar el iterador resultante en un bucle o convertirlo en una lista usando la función ''list()''
 + 
 +¿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() 
 + 
 +</code> 
 +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 ''map()'' junto con la primer lambda para crear una nueva lista en la que todos los elementos han sido evaluados como 2 elevado a la potencia tomada del elemento correspondiente de list_1. 
 +  * list_2 se imprime. 
 +  * En el siguiente paso, se usa nuevamente la función ''map()'' para hacer uso del generador que devuelve, e imprimir directamente todos los valores que entrega; como puedes ver, hemos usado la segunda lambda aquí - solo eleva al cuadrado cada elemento de list_2. 
 +  *  
 +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 ''filter()''
 + 
 +Espera el mismo tipo de argumentos que ''map()'', pero hace algo diferente: **filtra su segundo argumento mientras es guiado por direcciones que fluyen desde la función especificada en el primer argumento** (la función se invoca para cada elemento de la lista, al igual que en ''map()'' ). 
 + 
 +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,10) for x in range(5)] 
 +filtered = list(filter(lambda x: x > 0 and x % 2 == 0, data)) 
 + 
 +print(data) 
 +print(filtered) 
 +</code> 
 + 
 +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 ''seed()'', para producir cinco valores enteros aleatorios de -10 a 10 usando la función ''randint()''
 + 
 +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: 
 +<code> 
 +[6, 3, 3, 2, -7] 
 +[6, 2] 
 +</code> 
 + 
 +== Una breve explicación de cierres 
 +Comencemos con una definición: **cierres es una técnica que permite almacenar valores a pesar de que el contexto en el que se crearon ya no existe**. ¿Complicado? Un poco. 
 + 
 +Analicemos un ejemplo simple: 
 +<code python> 
 +def outer(par): 
 +    loc = par 
 + 
 + 
 +var = 1 
 +outer(var) 
 + 
 +print(var) 
 +print(loc) 
 +</code> 
 + 
 +El ejemplo es obviamente erróneo. 
 + 
 +Las dos últimas líneas provocarán una excepción //NameError// - ni par ni loc son accesibles fuera de la función. Ambas variables existen cuando y solo cuando la función ''outer()'' esta siendo ejecutada. 
 + 
 +<code python> 
 +def outer(par): 
 +    loc = par 
 + 
 +    def inner(): 
 +        return loc 
 +    return inner 
 + 
 + 
 +var = 1 
 +fun = outer(var) 
 +print(fun()) 
 +</code> 
 + 
 +Hay un elemento completamente nuevo - una función (llamada //inner//) dentro de otra función (llamada //outer//). 
 + 
 +¿Cómo funciona? Como cualquier otra función excepto por el hecho de que ''inner()'' solo se puede invocar desde dentro de ''outer()''. Podemos decir que ''inner()'' es una herramienta privada de ''outer()'', ninguna otra parte del código la puede acceder. 
 + 
 +Observa cuidadosamente: 
 +  * La función ''inner()'' devuelve el valor de la variable accesible dentro de su alcance, ya que ''inner()'' puede utilizar cualquiera de las entidades a disposición de ''outer()''
 +  * La función ''outer()'' devuelve la función ''inner()'' por si misma; mejor dicho, devuelve una copia de la función ''inner()'' al momento de la invocación de la función ''outer()''; la función congelada contiene su entorno completo, incluido el estado de todas las variables locales, lo que también significa que el valor de loc se retiene con éxito, aunque ''outer()'' ya ha dejado de existir. 
 + 
 +En efecto, el código es totalmente válido y genera: 
 +<code>1</code> 
 + 
 +La función devuelta durante la invocación de ''outer()'' es un **cierre**. 
 + 
 +**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()) 
 +</code> 
 + 
 +La función ''inner()'' no tenía parámetros, por lo que tuvimos que invocarla sin argumentos. 
 + 
 +<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)) 
 + 
 +</code> 
 +Es totalmente posible **declarar un cierre equipado con un número arbitrario de parámetros**, por ejemplo, al igual que la función ''power()''
 + 
 +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: puedes **crear tantos cierres como quieras usando el mismo código**. Esto se hace con una función llamada ''make_closure()''. Nota: 
 +  * 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: 
 +<code> 
 +0 0 0 
 +1 1 1 
 +2 4 8 
 +3 9 27 
 +4 16 64 
 +</code> 
 + 
 +== Puntos Clave 
 +1. Un **iterator** es un objeto de una clase que proporciona al menos **dos** métodos (sin contar el constructor): 
 +  * ''%%__iter__%%()'' se invoca una vez cuando se crea el iterador y devuelve el **propio** objeto del iterador. 
 +  * ''%%__next__%%()'' se invoca para proporcionar **el valor de la siguiente iteración** y genera la excepción StopIteration cuando la iteración **llega a su fin**. 
 + 
 +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) 
 +</code> 
 + 
 +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) 
 +</code> 
 +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, lambda x: x ** 0.5)) 
 +</code> 
 + 
 +Da como salida: 3.0. 
 + 
 + 
 +6. La función ''map(fun, list)'' crea una copia del argumento list, y aplica la función fun a todos sus elementos, retornando un generador que proporciona el nuevo contenido de la lista elemento por elemento. Por ejemplo: 
 +<code python> 
 +short_list = ['mython', 'python', 'fell', 'on', 'the', 'floor'
 +new_list = list(map(lambda s: s.title(), short_list)) 
 +print(new_list) 
 +</code> 
 + 
 +Da como salida: ['Mython', 'Python', 'Fell', 'On', 'The', 'Floor']. 
 + 
 +7. La función ''filter(fun, list)'' crea una copia de aquellos elementos de list, lo cual hace que la función fun retorne True. El resultado de la función es un generador proporcionando el nuevo contenido de la lista elemento por elemento. Por ejemplo 
 +<code python> 
 +short_list = [1, "Python", -1, "Monty"
 +new_list = list(filter(lambda s: isinstance(s, str), short_list)) 
 +print(new_list) 
 +</code> 
 + 
 +Da como salida: ['Python', 'Monty']. 
 + 
 +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] + '/' + tg[1:] 
 + 
 +    def inner(str): 
 +        return tg + str + tg2 
 +    return inner 
 + 
 +b_tag = tag('<b>'
 +print(b_tag('Monty Python')) 
 +</code> 
 + 
 +Da como salida: <b>Monty Python</b> 
 + 
  
-https://edube.org/learn/python-essentials-2-esp/generadores-y-cierres-22 
  
  • info/cursos/netacad/python/pe2m4/generadores.1657119665.txt.gz
  • Darrera modificació: 06/07/2022 08:01
  • per mate