Diferències
Ací es mostren les diferències entre la revisió seleccionada i la versió actual de la pàgina.
Següent revisió | Revisió prèvia | ||
info:cursos:netacad:python:pe2m4:generadores [05/07/2022 12:55] – creat mate | info:cursos:netacad:python:pe2m4:generadores [07/07/2022 07:12] (actual) – [Listas por comprensión frente a generadores] mate | ||
---|---|---|---|
Línia 1: | Línia 1: | ||
= Módulo 4 (Intermedio): | = Módulo 4 (Intermedio): | ||
- | https://edube.org/learn/python-essentials-2-esp/ | + | == Generadores, |
+ | **Generador** - ¿Con qué asocias esta palabra? Quizás se refiere a algún dispositivo electrónico. O tal vez se refiere a una máquina pesada diseñada para producir energía eléctrica u otra cosa. | ||
+ | |||
+ | Un generador de Python es **un fragmento de código especializado capaz de producir una serie de valores y controlar el proceso de iteración**. Esta es la razón por la cual los generadores a menudo se llaman **iteradores**, | ||
+ | |||
+ | Puede que no te hayas dado cuenta, pero te has topado con generadores muchas, muchas veces antes. Echa un vistazo al fragmento de código: | ||
+ | <code python> | ||
+ | for i in range(5): | ||
+ | print(i) | ||
+ | </code> | ||
+ | |||
+ | La función '' | ||
+ | |||
+ | ¿Cuál es la diferencia? | ||
+ | |||
+ | Una función devuelve un valor bien definido, el cual, puede ser el resultado de una evaluación compleja, por ejemplo, de un polinomio, y se invoca una vez, solo una vez. | ||
+ | |||
+ | Un generador **devuelve una serie de valores**, y en general, se invoca (implícitamente) más de una vez. | ||
+ | |||
+ | En el ejemplo, el generador '' | ||
+ | |||
+ | El proceso anterior es completamente transparente. Vamos a arrojar algo de luz sobre el. Vamos a mostrarte el protocolo iterador. | ||
+ | |||
+ | El **protocolo iterador es una forma en que un objeto debe comportarse para ajustarse a las reglas impuestas por el contexto de las sentencias | ||
+ | |||
+ | Un iterador debe proporcionar dos métodos: | ||
+ | |||
+ | * '' | ||
+ | * '' | ||
+ | |||
+ | ¿Suena extraño? De ningúna manera: | ||
+ | < | ||
+ | class Fib: | ||
+ | def __init__(self, | ||
+ | print(" | ||
+ | self.__n = nn | ||
+ | self.__i = 0 | ||
+ | self.__p1 = self.__p2 = 1 | ||
+ | |||
+ | def __iter__(self): | ||
+ | print(" | ||
+ | return self | ||
+ | |||
+ | def __next__(self): | ||
+ | print(" | ||
+ | self.__i += 1 | ||
+ | if self.__i > self.__n: | ||
+ | raise StopIteration | ||
+ | if self.__i in [1, 2]: | ||
+ | return 1 | ||
+ | ret = self.__p1 + self.__p2 | ||
+ | self.__p1, self.__p2 = self.__p2, ret | ||
+ | return ret | ||
+ | |||
+ | |||
+ | for i in Fib(10): | ||
+ | print(i) | ||
+ | |||
+ | </ | ||
+ | |||
+ | Hemos creado una clase capaz de iterar a través de los primeros n valores (donde n es un parámetro del constructor) de los números de Fibonacci. | ||
+ | |||
+ | Permítenos recordarte: los números de Fibonacci(Fibi) se definen de la siguiente manera: | ||
+ | |||
+ | Fib< | ||
+ | Fib< | ||
+ | Fib< | ||
+ | |||
+ | En otras palabras: | ||
+ | |||
+ | * Los primeros dos números de la serie Fibonacci son 1. | ||
+ | * Cualquier otro número de Fibonacci es la suma de los dos anteriores (por ejemplo, Fib3 = 2, Fib4 = 3, Fib5 = 5, y así sucesivamente). | ||
+ | |||
+ | Vamos a ver el código: | ||
+ | |||
+ | * Las líneas 2 a 6: el constructor de la clase imprime un mensaje (lo usaremos para rastrear el comportamiento de la clase), se preparan algunas variables: (%%__%%n para almacenar el límite de la serie, %%__%%i para rastrear el número actual de la serie Fibonacci, y %%__%%p1 junto con %%__%%p2 para guardar los dos números anteriores). | ||
+ | * Las líneas 8 a 10: el método %%__iter__%% está obligado a devolver el objeto iterador en sí mismo; su propósito puede ser un poco ambiguo aquí, pero no hay misterio; trata de imaginar un objeto que no sea un iterador (por ejemplo, es una colección de algunas entidades), pero uno de sus componentes es un iterador capaz de escanear la colección; el método %%__iter__%% debe **extraer el iterador y confiarle la ejecución del protocolo de iteración**; | ||
+ | * Las líneas 12 a 21: el método %%__next__%% es responsable de crear la secuencia; es algo largo, pero esto debería hacerlo más legible; primero, imprime un mensaje, luego actualiza el número de valores deseados y, si llega al final de la secuencia, el método interrumpe la iteración al generar la excepción StopIteration; | ||
+ | * Las líneas 24 y 25 hacen uso del iterador. | ||
+ | |||
+ | El código produce el siguiente resultado: | ||
+ | < | ||
+ | __init__ | ||
+ | __iter__ | ||
+ | __next__ | ||
+ | 1 | ||
+ | __next__ | ||
+ | 1 | ||
+ | __next__ | ||
+ | 2 | ||
+ | __next__ | ||
+ | 3 | ||
+ | __next__ | ||
+ | 5 | ||
+ | __next__ | ||
+ | 8 | ||
+ | __next__ | ||
+ | 13 | ||
+ | __next__ | ||
+ | 21 | ||
+ | __next__ | ||
+ | 34 | ||
+ | __next__ | ||
+ | 55 | ||
+ | __next__ | ||
+ | </ | ||
+ | |||
+ | Observa: | ||
+ | * El objeto iterador se instancia primero. | ||
+ | * Después, Python invoca el método %%__iter__%% para acceder al iterador real. | ||
+ | * El método %%__next__%% se invoca once veces: las primeras diez veces produce valores útiles, mientras que la ultima finaliza la iteración. | ||
+ | |||
+ | El ejemplo muestra una solución donde el **objeto iterador es parte de una clase más compleja**. | ||
+ | |||
+ | <code python> | ||
+ | class Fib: | ||
+ | def __init__(self, | ||
+ | self.__n = nn | ||
+ | self.__i = 0 | ||
+ | self.__p1 = self.__p2 = 1 | ||
+ | |||
+ | def __iter__(self): | ||
+ | print(" | ||
+ | return self | ||
+ | |||
+ | def __next__(self): | ||
+ | self.__i += 1 | ||
+ | if self.__i > self.__n: | ||
+ | raise StopIteration | ||
+ | if self.__i in [1, 2]: | ||
+ | return 1 | ||
+ | ret = self.__p1 + self.__p2 | ||
+ | self.__p1, self.__p2 = self.__p2, ret | ||
+ | return ret | ||
+ | |||
+ | |||
+ | class Class: | ||
+ | def __init__(self, | ||
+ | self.__iter = Fib(n) | ||
+ | |||
+ | def __iter__(self): | ||
+ | print(" | ||
+ | return self.__iter | ||
+ | |||
+ | |||
+ | object = Class(8) | ||
+ | |||
+ | for i in object: | ||
+ | print(i) | ||
+ | </ | ||
+ | |||
+ | El código no es sofisticado, | ||
+ | |||
+ | Echa un vistazo al código en el editor. | ||
+ | |||
+ | Hemos colocado el iterador //Fib// dentro de otra clase (podemos decir que lo hemos compuesto dentro de la clase //Class//). Se instancia junto con el objeto de // | ||
+ | |||
+ | El objeto de la clase se puede usar como un iterador cuando (y solo cuando) responde positivamente a la invocación %%__iter__%% | ||
+ | |||
+ | Es por eso que la salida del código es la misma que anteriormente, | ||
+ | |||
+ | < | ||
+ | Class iter | ||
+ | 1 | ||
+ | 1 | ||
+ | 2 | ||
+ | 3 | ||
+ | 5 | ||
+ | 8 | ||
+ | 13 | ||
+ | 21 | ||
+ | </ | ||
+ | |||
+ | == La sentencia yield | ||
+ | El protocolo iterador no es difícil de entender y usar, pero también es indiscutible que **el protocolo es bastante inconveniente**. | ||
+ | |||
+ | La principal molestia que tiene es que **necesita guardar el estado de la iteración en las invocaciones subsecuentes de %%__iter__%%**. | ||
+ | |||
+ | Por ejemplo, el iterador //Fib// se ve obligado a almacenar con precisión el lugar en el que se detuvo la última invocación (es decir, el número evaluado y los valores de los dos elementos anteriores). Esto hace que el código sea más grande y menos comprensible. | ||
+ | |||
+ | Es por eso que Python ofrece una forma mucho más efectiva, conveniente y elegante de escribir iteradores. | ||
+ | |||
+ | El concepto se basa fundamentalmente en un mecanismo muy específico proporcionado por la palabra clave reservada // | ||
+ | |||
+ | Se puede ver a la palabra clave reservada //yield// como un hermano más inteligente de la sentencia //return//, con una diferencia esencial. | ||
+ | |||
+ | Echa un vistazo a esta función: | ||
+ | <code python> | ||
+ | def fun(n): | ||
+ | for i in range(n): | ||
+ | return i | ||
+ | </ | ||
+ | |||
+ | Se ve extraño, ¿no? Está claro que el bucle //for// no tiene posibilidad de terminar su primera ejecución, ya que el //return// lo romperá irrevocablemente. | ||
+ | |||
+ | Además, invocar la función no cambiará nada: el bucle //for// comenzará desde cero y se romperá inmediatamente. | ||
+ | |||
+ | Podemos decir que dicha función no puede guardar y restaurar su estado en invocaciones posteriores. | ||
+ | |||
+ | Esto también significa que una función como esta **no se puede usar como generador**. | ||
+ | |||
+ | Hemos reemplazado exactamente una palabra en el código, ¿puedes verla? | ||
+ | <code python> | ||
+ | def fun(n): | ||
+ | for i in range(n): | ||
+ | yield i | ||
+ | </ | ||
+ | |||
+ | Hemos puesto //yield// en lugar de //return//. Esta pequeña enmienda convierte la función en un generador, y el ejecutar la sentencia //yield// tiene algunos efectos muy interesantes. | ||
+ | |||
+ | Primeramente, | ||
+ | |||
+ | Todos los valores de las variables están congelados y esperan la próxima invocación, | ||
+ | |||
+ | Hay una limitación importante: dicha **función no debe invocarse explícitamente** ya que no es una función; **es un objeto generador**. | ||
+ | |||
+ | La invocación devolverá **el identificador del objeto**, no la serie que esperamos del generador. | ||
+ | |||
+ | Debido a las mismas razones, la función anterior (la que tiene el return) solo se puede invocar explícitamente y no se debe usar como generador. | ||
+ | |||
+ | === Cómo construir un generador: | ||
+ | Permítenos mostrarte el nuevo generador en acción. | ||
+ | |||
+ | Así es como podemos usarlo: | ||
+ | <code python> | ||
+ | def fun(n): | ||
+ | for i in range(n): | ||
+ | yield i | ||
+ | |||
+ | |||
+ | for v in fun(5): | ||
+ | print(v) | ||
+ | </ | ||
+ | |||
+ | ¿Puedes adivinar la salida? | ||
+ | < | ||
+ | 0 | ||
+ | 1 | ||
+ | 2 | ||
+ | 3 | ||
+ | 4 | ||
+ | </ | ||
+ | |||
+ | == Cómo construir tu propio generador | ||
+ | ¿Qué pasa si necesitas un **generador para producir las primeras n potencias de 2** ? | ||
+ | |||
+ | Nada difícil. Solo mira el código en el editor. | ||
+ | <code python> | ||
+ | def powers_of_2(n): | ||
+ | power = 1 | ||
+ | for i in range(n): | ||
+ | yield power | ||
+ | power *= 2 | ||
+ | |||
+ | |||
+ | for v in powers_of_2(8): | ||
+ | print(v) | ||
+ | </ | ||
+ | |||
+ | ¿Puedes adivinar la salida? Ejecuta el código para verificar tus conjeturas. | ||
+ | |||
+ | |||
+ | === Listas por comprensión | ||
+ | Los generadores | ||
+ | <code python> | ||
+ | def powers_of_2(n): | ||
+ | power = 1 | ||
+ | for i in range(n): | ||
+ | yield power | ||
+ | power *= 2 | ||
+ | |||
+ | |||
+ | t = [x for x in powers_of_2(5)] | ||
+ | print(t) | ||
+ | </ | ||
+ | |||
+ | Ejecuta el ejemplo y verifica la salida. | ||
+ | |||
+ | |||
+ | === La función list() | ||
+ | |||
+ | La función '' | ||
+ | <code python> | ||
+ | def powers_of_2(n): | ||
+ | power = 1 | ||
+ | for i in range(n): | ||
+ | yield power | ||
+ | power *= 2 | ||
+ | |||
+ | |||
+ | t = list(powers_of_2(3)) | ||
+ | print(t) | ||
+ | </ | ||
+ | |||
+ | Nuevamente, intenta predecir el resultado y ejecuta el código para verificar tus predicciones. | ||
+ | |||
+ | |||
+ | === El operador in | ||
+ | Además, el contexto creado por el operador //in// también te permite usar un generador. | ||
+ | |||
+ | El ejemplo muestra cómo hacerlo: | ||
+ | <code python> | ||
+ | def powers_of_2(n): | ||
+ | power = 1 | ||
+ | for i in range(n): | ||
+ | yield power | ||
+ | power *= 2 | ||
+ | |||
+ | |||
+ | for i in range(20): | ||
+ | if i in powers_of_2(4): | ||
+ | print(i) | ||
+ | </ | ||
+ | |||
+ | ¿Cuál es la salida del código? Ejecuta el programa y verifica. | ||
+ | |||
+ | |||
+ | === El generador de números Fibonacci | ||
+ | Ahora veamos un generador de números de la serie Fibonacci, asegurandonos que se vea mucho mejor que la versión orientada a objetos basada en la implementación directa del protocolo iterador. | ||
+ | |||
+ | Aquí está: | ||
+ | <code python> | ||
+ | def fibonacci(n): | ||
+ | p = pp = 1 | ||
+ | for i in range(n): | ||
+ | if i in [0, 1]: | ||
+ | yield 1 | ||
+ | else: | ||
+ | n = p + pp | ||
+ | pp, p = p, n | ||
+ | yield n | ||
+ | |||
+ | fibs = list(fibonacci(10)) | ||
+ | print(fibs) | ||
+ | </ | ||
+ | |||
+ | Adivina la salida (una lista) producida por el generador y ejecuta el código para verificar si tenías razón. | ||
+ | |||
+ | Debes poder recordar las reglas que rigen la creación y el uso de un fenómeno de Python llamado **listas por comprensión: | ||
+ | |||
+ | <code python> | ||
+ | list_1 = [] | ||
+ | |||
+ | for ex in range(6): | ||
+ | list_1.append(10 ** ex) | ||
+ | |||
+ | list_2 = [10 ** ex for ex in range(6)] | ||
+ | |||
+ | print(list_1) | ||
+ | print(list_2) | ||
+ | </ | ||
+ | |||
+ | Existen dos partes dentro del código, ambas crean una lista que contiene algunas de las primeras potencias naturales de diez. | ||
+ | |||
+ | La primer parte utiliza una forma rutinaria del bucle //for//, mientras que la segunda hace uso de listas por comprensión y construye la lista en el momento, sin necesidad de un bucle o cualquier otro código. | ||
+ | |||
+ | Pareciera que la lista se crea dentro de sí misma; esto es falso, ya que Python tiene que realizar casi las mismas operaciones que en la primera parte, pero el segundo formalismo es simplemente más elegante y le evita al lector cualquier detalle innecesario. | ||
+ | |||
+ | El ejemplo genera dos líneas idénticas que contienen el siguiente texto: | ||
+ | < | ||
+ | [1, 10, 100, 1000, 10000, 100000] | ||
+ | [1, 10, 100, 1000, 10000, 100000] | ||
+ | </ | ||
+ | |||
+ | Hay una sintaxis muy interesante que queremos mostrarte ahora. Su usabilidad no se limita a listas por comprensión. | ||
+ | |||
+ | Es una **expresión condicional: | ||
+ | |||
+ | Observa: | ||
+ | < | ||
+ | expresión_uno if condición else expresión_dos | ||
+ | </ | ||
+ | |||
+ | Puede parecer un poco sorprendente a primera vista, pero hay que tener en cuenta que **no es una instrucción condicional**. Además, no es una instrucción en lo absoluto. Es un operador. | ||
+ | |||
+ | El valor que proporciona es expresión_uno cuando la condición es True (verdadero), | ||
+ | |||
+ | <code python> | ||
+ | the_list = [] | ||
+ | |||
+ | for x in range(10): | ||
+ | the_list.append(1 if x % 2 == 0 else 0) | ||
+ | |||
+ | print(the_list) | ||
+ | </ | ||
+ | |||
+ | El código llena una lista con //unos// y //ceros//, si el índice de un elemento particular es impar, el elemento se establece a 0, y a 1 de lo contrario. | ||
+ | |||
+ | ¿Simple? Quizás no a primera vista. ¿Elegante? Indiscutiblemente. | ||
+ | |||
+ | ¿Se puede usar el mismo truco dentro de una comprensión de lista? Sí, por supuesto. | ||
+ | |||
+ | <code python> | ||
+ | the_list = [1 if x % 2 == 0 else 0 for x in range(10)] | ||
+ | |||
+ | print(the_list) | ||
+ | </ | ||
+ | |||
+ | Compacto y elegante: estas dos palabras vienen a la mente al mirar el código. | ||
+ | |||
+ | Entonces, ¿qué tienen en común, generadores y listas por comprensión? | ||
+ | |||
+ | Solo un cambio puede **convertir cualquier comprensión en un generador**. | ||
+ | |||
+ | |||
+ | === Listas por comprensión frente a generadores | ||
+ | |||
+ | Ahora observa el código a continuación y ve si puedes encontrar el detalle que convierte una comprensión de lista en un generador: | ||
+ | <code python> | ||
+ | the_list = [1 if x % 2 == 0 else 0 for x in range(10)] | ||
+ | the_generator = (1 if x % 2 == 0 else 0 for x in range(10)) | ||
+ | |||
+ | for v in the_list: | ||
+ | print(v, end=" ") | ||
+ | print() | ||
+ | |||
+ | for v in the_generator: | ||
+ | print(v, end=" ") | ||
+ | print() | ||
+ | </ | ||
+ | |||
+ | Son los **paréntesis**. Los corchetes hacen una comprensión, | ||
+ | |||
+ | El código, cuando se ejecuta, produce dos líneas idénticas: | ||
+ | < | ||
+ | 1 0 1 0 1 0 1 0 1 0 | ||
+ | 1 0 1 0 1 0 1 0 1 0 | ||
+ | </ | ||
+ | |||
+ | ¿Cómo puedes saber que la segunda asignación crea un generador, no una lista? | ||
+ | |||
+ | Hay algunas pruebas que podemos mostrarte. Aplica la función '' | ||
+ | |||
+ | '' | ||
+ | < | ||
+ | |||
+ | Por supuesto, guardar la lista o el generador no es necesario; puedes crearlos exactamente en el lugar donde los necesites, justo como aquí: | ||
+ | <code python> | ||
+ | for v in [1 if x % 2 == 0 else 0 for x in range(10)]: | ||
+ | print(v, end=" ") | ||
+ | print() | ||
+ | |||
+ | for v in (1 if x % 2 == 0 else 0 for x in range(10)): | ||
+ | print(v, end=" ") | ||
+ | print() | ||
+ | </ | ||
+ | |||
+ | Nota: la misma apariencia de la salida no significa que ambos bucles funcionen de la misma manera. En el primer bucle, la lista se crea (y se itera) como un todo; en realidad, existe cuando se ejecuta el bucle. | ||
+ | |||
+ | En el segundo bucle, no hay ninguna lista, solo hay valores subsecuentes producidos por el generador, uno por uno. | ||
+ | |||
+ | == 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, | ||
+ | |||
+ | 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: < | ||
+ | |||
+ | |||
+ |