mi_cadena = "abcd"
print("\nIteración sobre una cadena: ", end='')
for char in mi_cadena:
print(char, end=' ')
Iteración sobre una cadena: a b c d
Objetivo. Revisar elementos que permiten optimizar códigos en Python.
En Python existen objetos que contienen secuencias de otros objetos.
La mayoría de los objetos contenedores se pueden recorrer usando un ciclo for.
Este es un estilo claro y conveniente que impregna el universo de Python.
Por ejemplo:
mi_cadena = "abcd"
print("\nIteración sobre una cadena: ", end='')
for char in mi_cadena:
print(char, end=' ')
Iteración sobre una cadena: a b c d
Notas importantes:
for llama a la función iter() que está definida dentro del objeto contenedor.iter() regresa como resultado un objeto iterador que define el método __next__, con el que se puede acceder a los elementos del objeto contenedor, uno a la vez.__next__() lanza una excepción de tipo StopIteration que le dice al ciclo for que debe terminar.__next__() del iterador usando la función de biblioteca next().Por ejemplo:
iterador = iter(mi_cadena) # Obtenemos un iterador para la cadena
print(type(iterador)) # Obtenemos el tipo del iterador<class 'str_ascii_iterator'>
Ahora aplicamos next() al iterador:
print(next(iterador)) # Aplicamos __next__() al iterador para obtener: a
print(next(iterador)) # Aplicamos __next__() al iterador para obtener: b
print(next(iterador)) # Aplicamos __next__() al iterador para obtener: c
print(next(iterador)) # Aplicamos __next__() al iterador para obtener: da
b
c
d
Cuando ya llegamos al final de la secuencia e intentamos aplicar __next__() obtenemos una excepción:
next(iterador) # Sobrepasó los elementos, se obtiene la excepción StopIteration--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) Cell In[4], line 1 ----> 1 next(iterador) # Sobrepasó los elementos, se obtiene la excepción StopIteration StopIteration:
Observa que cuando se hace el recorrido de la cadena usando el ciclo for no se produce ninguna excepción debido a que maneja la excepción para terminar el proceso adecuadamente.
Se puede crear un iterador y aplicarle la función next() a cualquier secuencia, por ejemplo a una lista
# Creación de una lista
cuadrados = [x*x for x in range(10)]
print(cuadrados)[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# Recorriendo la lista usando un iterador en una lista concisa:
iterador = iter(cuadrados)
[next(iterador) for x in range(10)][0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Estos objetos iterables son manejables y prácticos debido a que se pueden usar tantas veces como se desee, pero se almacenan todos los valores en memoria y esto no siempre es conveniente, sobre todo cuando se tienen muchos valores.
Los objetos generadores son iteradores.
Pero solo se puede iterar sobre ellos una sola vez. Esto es porque los generadores no almacenan todos los valores en memoria, ellos generan los valores al vuelo.
Un generador se crea como sigue:
(expresion for x in secuencia)
donde expresion es una expresión válida de Python que genera los elementos del generador; x es un elemento al que se le aplica la expresion y secuencia es cualquier secuencia válida en Python.
Por ejemplo:
# Un generador simple
gen = (x for x in range(3))# Recorremos la secuencia aplicando next() al generador
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen)) # Produce una excepción de tipo StopIteration0
1
2
--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) Cell In[8], line 5 3 print(next(gen)) 4 print(next(gen)) ----> 5 print(next(gen)) # Produce una excepción de tipo StopIteration StopIteration:
# Creamos el generador
generador = (x*x for x in range(10))
print(type(generador))
# Recorremos el generador en un ciclo for
for i in generador:
print(i, end=' ')<class 'generator'>
0 1 4 9 16 25 36 49 64 81
En el ejemplo anterior tenemos:
Un generador solo se puede usar una vez, pues va calculando sus valores uno por uno e inmediatamente los va olvidando. Si intentamos utilizar una vez más el generador, ya no obtendremos nada:
for i in generador: # Este ciclo no imprimirá nada por que
print(i, end=' ') # el generador ya se usó antesObserva que no se produce un error porque estamos usando el generador dentro del ciclo for.
Es una palabra clave que suspende la ejecución de una función y envía un valor de regreso a quien la ejecuta, pero retiene la información suficiente para reactivar la ejecución de la función donde se quedó. Si la función se vuelve a ejecutar, se reanuda desde donde se detuvo la última vez.
Esto permite al código producir una serie de valores uno por uno, en vez de calcularlos y regresarlos todos.
Una función que contiene la declaración yield se le conoce como función generadora.
Por ejemplo:
# Función generadora
def generadorSimple():
print('yield 1 : ', end=' ')
yield 1
print('yield 2 : ', end=' ')
yield 23
print('yield 3 : ', end=' ')
yield 35
# Se construye un generador
gen = generadorSimple()
# Se usa el generador
print('Primera ejecución de la función generadora: {}'.format(next(gen)))
print('Segunda ejecución de la función generadora: {}'.format(next(gen)))
print('Tercera ejecución de la función generadora: {}'.format(next(gen)))yield 1 : Primera ejecución de la función generadora: 1
yield 2 : Segunda ejecución de la función generadora: 23
yield 3 : Tercera ejecución de la función generadora: 35
Si se intenta usar una vez más el generador obtendremos una excepción de tipo StopIteration:
print('Cuarta ejecución de la función generadora: {}'.format(next(gen)))--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) Cell In[14], line 1 ----> 1 print('Cuarta ejecución de la función generadora: {}'.format(next(gen))) StopIteration:
Notas importantes.
yield es usada como un return, excepto que la función regresa un objeto generador.for o while.Entonces, una función generadora regresa un objeto generador que es iterable, es decir, se puede usar como un iterador.
def construyeUnGenerador(v):
for i in range(v):
yield i*i
# Se construye una función generadora
cuadradosY = construyeUnGenerador(10)
print(type(cuadradosY))
for i in cuadradosY:
print(i)<class 'generator'>
0
1
4
9
16
25
36
49
64
81
Se recomienda usar yield cuando se desea iterar sobre una secuencia, pero no se quiere almacenar toda la secuencia en memoria.
# Función generadora que genera el cuadrado de un número
def cuadradoSiguiente():
i = 1;
while True:
yield i*i
i += 1 # La siguiente ejecución se
# reactiva en este punto
for numero in cuadradoSiguiente():
if numero > 100:
break
print(numero)1
4
9
16
25
36
49
64
81
100
.
# Función generadora
def fib(limite):
a, b = 0, 1
while a < limite:
yield a
a, b = b, a + b # La siguiente iteración se reactiva en este puntoN = 100
# Generador
x = fib(N)
while True:
try:
print(next(x), end=' ') # Usamos la función next() para iterar
except StopIteration: # Manejamos la excepción
break0 1 1 2 3 5 8 13 21 34 55 89
# Usando la función generadora directamente en un ciclo for
for i in fib(N):
print(i, end=' ')0 1 1 2 3 5 8 13 21 34 55 89