8. Generadores / Iteradores¶
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,
El protocolo iterador es una forma en que un objeto debe comportarse para ajustarse a las reglas impuestas por el contexto de las sentencias for e in. un objeto conforme al protocolo iterador se llama iterador.
Un iterador debe proporcionar dos métodos:
__iter__()
el cual debe devolver el objeto en sí y que se invoca una vez (es necesario para que Python inicie con éxito la iteración).
__next__()
el cual debe devolver el siguiente valor (primero, segundo, etc.) de la serie deseada: será invocado por las sentencias for/in para pasar a la siguiente iteración; si no hay más valores a proporcionar, el método deberá generar la excepción StopIteration cuando la iteración llega a su fin.
class Fib:
def __init__(self, nn):
self.__n = nn
self.__i = 0
self.__p1 = self.__p2 = 1
def __iter__(self):
print("Fib iter")
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, n):
self.__iter = Fib(n)
def __iter__(self):
print("Class iter")
return self.__iter
object = Class(8)
for i in object:
print(i)
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__
.
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).
def fun(n):
for i in range(n):
yield i
for v in fun(5):
print(v)
Expresiones condicionales¶
Una expresión condicional es una expresión construida usando el operador if-else. Por ejemplo:
print(True if 0 >= 0 else False)
Construir tu propio generador¶
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)
Usar generador con listas por comprensión¶
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:
for x in (el * 2 for el in range(5)):
print(x)
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)
Una forma de seleccionar uno de dos valores diferentes en función del resultado de una expresión Booleana:
expresión_uno if condición else expresión_dos
the_list = []
for x in range(10):
the_list.append(1 if x % 2 == 0 else 0)
print(the_list)
the_list = [1 if x % 2 == 0 else 0 for x in range(10)]
print(the_list)
Lambda¶
Una función lambda es una herramienta para crear funciones anónimas. Por ejemplo:
def foo(x, f):
return f(x)
print(foo(9, lambda x: x ** 0.5))
Una función lambda es una función sin nombre (también puedes llamarla una función anónima).
lambda parámetros: expresión
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()))
Map¶
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.
short_list = ['mython', 'python', 'fell', 'on', 'the', 'floor']
new_list = list(map(lambda s: s.title(), short_list))
print(new_list)
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()
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.
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)
short_list = [1, "Python", -1, "Monty"]
new_list = list(filter(lambda s: isinstance(s, str), short_list))
print(new_list)
Cierres¶
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:
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'))