Iteradores y generadores en Python — práctica con Fibonacci sin fin, una tubería de primos y la cola de reservas de la biblioteca (y el portátil que se quedó pensando)
Para un ejercicio de clase necesitaba «unos cuantos» números de Fibonacci y, por si acaso, generé una lista con los primeros diez millones. El portátil se quedó pensando, el ventilador despegó y al final ni siquiera llegué a usarlos: solo necesitaba los que no pasaban de 100. La lección que me llevé es la de todo este artículo: no calcules por adelantado lo que no sabes si vas a necesitar; pide los valores de uno en uno y para cuando tengas bastante.
En la teoría de iteradores y generadores viste cómo funciona el for por dentro, por qué yield pausa en lugar de terminar y por qué un generador se gasta. Hoy lo ponemos a trabajar con tres programas de nivel medio: un generador infinito de Fibonacci, una tubería de generadores de números primos y la cola de reservas de un libro de la biblioteca, hecha con nodos enlazados y recorrida con un generador. Los tres usan datos que se parecen a los de verdad: secuencias que no terminan, límites que no sabes de antemano y una cola que cambia mientras la usas.
Tabla de Contenidos
Antes de empezar
Conviene tener claro:
- Que un
forhaceiter()y despuésnext()hasta que saltaStopIteration. - Que una función con
yielddevuelve un generador y no ejecuta nada hasta el primernext(). - Que
returndentro de un generador lo termina (y elforque lo recorre simplemente acaba). - Que un generador solo se recorre una vez, y una lista, las que quieras.
Si alguno te suena dudoso, repasa la teoría antes de seguir.
Los tres programas usan datos fijos (nada de input()) y no necesitan instalar nada: copia cada uno en un archivo .py y ejecútalo con python3 archivo.py. Las salidas que verás aquí son las reales, de una sola ejecución con Python 3.13.
Programa 1: generador de Fibonacci infinito
La sucesión de Fibonacci (0, 1, 1, 2, 3, 5, 8…) es el ejemplo perfecto de secuencia sin final: cada número es la suma de los dos anteriores. El programa tiene que dar los 10 primeros, los que no pasan de 100, los pares hasta 500, el primero que llega a 1000 (con su posición) y ver cómo el cociente entre dos seguidos se acerca a la razón áurea. Lo que lo complica: cada pregunta necesita una cantidad distinta de números, y no sabemos cuántos de antemano.
La idea
Un solo generador infinito, fibonacci(), que solo guarda dos números (el anterior y el actual) y entrega uno en cada next(). Todo lo demás son formas distintas de dejar de pedir: primeros(n, gen) corta por cantidad, hasta(limite, gen) corta por valor (con return), y un for con break corta por una condición cualquiera. El generador no sabe quién lo usa ni cuántos le van a pedir; solo sabe dar el siguiente.
Antes de programar, piensa en cómo pasar de (anterior, actual) = (0, 1) a (1, 1) y después a (1, 2) sin perder ningún valor por el camino.
El código
def fibonacci():
"""Generador infinito de la sucesión de Fibonacci: 0, 1, 1, 2, 3, 5..."""
anterior, actual = 0, 1
while True:
yield anterior
anterior, actual = actual, anterior + actual # la derecha se calcula ANTES de asignar
def hasta(limite, gen):
"""Deja pasar los valores de gen mientras no superen el límite."""
for x in gen:
if x > limite:
return # return en un generador = se acabó (StopIteration)
yield x
def primeros(n, gen):
"""Los n primeros valores de cualquier generador (como itertools.islice)."""
for _ in range(n):
yield next(gen)
print('Primeros 10:', list(primeros(10, fibonacci())))
print('Hasta 100: ', list(hasta(100, fibonacci())))
pares = [n for n in hasta(500, fibonacci()) if n % 2 == 0]
print('Pares hasta 500:', pares)
for pos, n in enumerate(fibonacci()): # enumerate también va pidiendo de uno en uno
if n >= 1000:
print(f'Primero >= 1000: {n} (posición {pos})')
break
g = fibonacci()
next(g) # saltamos el 0 para no dividir entre 0
a = next(g)
for i in range(1, 21):
b = next(g)
if i in (5, 10, 20):
print(f'Cociente tras {i:2} pasos: {b / a:.6f}')
a = b
La línea más importante es anterior, actual = actual, anterior + actual. Python calcula toda la parte derecha antes de asignar nada: primero forma la tupla (actual, anterior + actual) con los valores viejos y después la reparte. Si lo escribieras en dos líneas (anterior = actual y luego actual = anterior + actual), la segunda usaría el anterior ya cambiado y la sucesión saldría mal.
En el dibujo, cada píldora violeta es el estado del generador en un next() y la etiqueta coral es lo que entrega. El generador nunca guarda más de dos números.
Las dos funciones auxiliares también son generadores:
def hasta(limite, gen):
"""Deja pasar los valores de gen mientras no superen el límite."""
for x in gen:
if x > limite:
return # return en un generador = se acabó (StopIteration)
yield x
def primeros(n, gen):
"""Los n primeros valores de cualquier generador (como itertools.islice)."""
for _ in range(n):
yield next(gen)
hastarecorre el generador que le pasan y, en cuanto un valor se pasa del límite, hacereturn. Eso terminahasta(y ellist()que la recorre acaba), aunquefibonacci()podría seguir dando números para siempre.primerosllamanveces anext(gen)y entrega cada valor. Es lo que haceitertools.islice(gen, n).
Salida
Primeros 10: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34] Hasta 100: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89] Pares hasta 500: [0, 2, 8, 34, 144] Primero >= 1000: 1597 (posición 17) Cociente tras 5 pasos: 1.600000 Cociente tras 10 pasos: 1.618182 Cociente tras 20 pasos: 1.618034
Comprobémoslo a mano. Los diez primeros: 0, 1, 0 + 1 = 1, 1 + 1 = 2, 1 + 2 = 3, 2 + 3 = 5, 3 + 5 = 8, 13, 21, 34. Hasta 100 llegan dos más (55 y 89); el siguiente, 144, ya se pasa y hasta hace return. Los pares hasta 500 son 0, 2, 8, 34 y 144: el siguiente par sería 610 (en Fibonacci, uno de cada tres números es par). El primero que llega a 1000 es 1597 = 610 + 987, en la posición 17 contando desde 0. Y los cocientes 8 / 5 = 1,6, después 1,618182 y 1,618034: la razón áurea (1,6180339…) aparece sola.
next() nº | anterior | actual | Entrega | Estado siguiente |
|---|---|---|---|---|
| 1 | 0 | 1 | 0 | (1, 0 + 1) = (1, 1) |
| 2 | 1 | 1 | 1 | (1, 1 + 1) = (1, 2) |
| 3 | 1 | 2 | 1 | (2, 1 + 2) = (2, 3) |
| 4 | 2 | 3 | 2 | (3, 2 + 3) = (3, 5) |
| 5 | 3 | 5 | 3 | (5, 3 + 5) = (5, 8) |
Preguntas que te puedes hacer
¿Por qué cada pregunta crea un fibonacci() nuevo? Porque un generador se gasta. Si reutilizáramos el mismo para «hasta 100» y después para «pares hasta 500», el segundo empezaría donde se quedó el primero (en 144) y nos saltaríamos el principio.
¿Por qué hasta usa return y no break? Dentro de un generador, return lo termina: es la forma limpia de decir «ya no hay más». Un break saldría del for interno y, como después no hay nada, el generador también acabaría; funcionaría igual, pero return deja más claro qué pasa.
¿Por qué saltamos el 0 antes de calcular los cocientes? Porque el cociente es b / a y el primer valor es 0: dividir entre 0 lanzaría ZeroDivisionError. Un next(g) suelto descarta ese primer valor.
¿Por qué enumerate(fibonacci()) no se queda colgado si el generador es infinito? Porque enumerate también es perezoso: pide de uno en uno. El break del for deja de pedir en la posición 17, y nadie calcula ni un número más.
list(fibonacci()). Sin un límite, list() intenta recorrer el generador entero, que no termina nunca: el programa se queda colgado sin dar error. A un generador infinito siempre se le pide con next(), islice, un for con break o una función como hasta().
Pruébalo tú
- Escribe
suma_hasta(limite), que devuelva la suma de los números de Fibonacci que no pasen del límite, usandosum()conhasta()y sin crear ninguna lista. - Cambia
fibonacci()para que reciba los dos primeros valores (fibonacci(2, 1)da la sucesión de Lucas: 2, 1, 3, 4, 7…).
Programa 2: generador de números primos
Un primo es un número mayor que 1 que solo se divide entre 1 y él mismo. El programa tiene que dar los 15 primeros, cuántos hay hasta 100, el primo número 50, los que hay entre 100 y 150 y las primeras parejas de primos gemelos (dos primos que se diferencian en 2, como 11 y 13). Lo que lo complica: comprobar si un número es primo cuesta trabajo, la secuencia no termina y cada pregunta corta en un sitio distinto.
La idea
Separar las responsabilidades en piezas pequeñas que se enchufan unas a otras como una tubería: es_primo(n) solo decide si un número es primo; primos() es el generador infinito que va probando candidatos; primos_hasta(limite), n_primos(n) y gemelos(gen) son generadores que consumen a primos() y transforman o cortan lo que sale. Cada número viaja por la tubería entera antes de que se calcule el siguiente.
Antes de programar, piensa hasta dónde hace falta probar divisores para saber que 97 es primo. ¿Hace falta llegar a 96?
El código
def es_primo(n):
"""True si n es primo. Solo prueba divisores impares hasta la raíz cuadrada."""
if n < 2:
return False
if n % 2 == 0:
return n == 2 # el 2 es el único par primo
i = 3
while i * i <= n: # si n tiene un divisor, uno de ellos es <= raíz de n
if n % i == 0:
return False
i += 2
return True
def primos():
"""Generador infinito de primos."""
yield 2
candidato = 3
while True:
if es_primo(candidato):
yield candidato
candidato += 2 # los pares (salvo el 2) no hace falta mirarlos
def primos_hasta(limite):
for p in primos(): # consume el generador infinito de uno en uno
if p > limite:
return
yield p
def n_primos(n):
contador = 0
for p in primos():
if contador == n:
return
yield p
contador += 1
def gemelos(gen):
"""Parejas de primos que se diferencian en 2, a partir de un generador de primos."""
anterior = next(gen)
for p in gen: # el for sigue por donde lo dejó el next()
if p - anterior == 2:
yield (anterior, p)
anterior = p
print('Primeros 15:', list(n_primos(15)))
print('Hasta 100 hay', sum(1 for _ in primos_hasta(100)), 'primos')
g = primos()
for _ in range(49):
next(g)
print('El primo número 50:', next(g))
print('Entre 100 y 150:', [p for p in primos_hasta(150) if p >= 100])
pares = gemelos(primos())
print('Primeras 5 parejas de gemelos:', [next(pares) for _ in range(5)])
La función es_primo tiene dos atajos que ahorran mucho trabajo:
def es_primo(n):
"""True si n es primo. Solo prueba divisores impares hasta la raíz cuadrada."""
if n < 2:
return False
if n % 2 == 0:
return n == 2 # el 2 es el único par primo
i = 3
while i * i <= n: # si n tiene un divisor, uno de ellos es <= raíz de n
if n % i == 0:
return False
i += 2
return True
- Los pares se resuelven en una línea:
return n == 2daTruesolo para el 2 yFalsepara el resto. - Solo se prueban divisores impares hasta la raíz cuadrada (
i * i <= n). Si n = a × b, uno de los dos factores es como mucho √n: si no hay divisor hasta ahí, no lo hay después. Para 97 basta probar 3, 5, 7 y 9 (11 × 11 = 121 ya se pasa): 4 divisiones en lugar de 95.
Y gemelos usa un detalle del protocolo que conviene ver de cerca:
def gemelos(gen):
"""Parejas de primos que se diferencian en 2, a partir de un generador de primos."""
anterior = next(gen)
for p in gen: # el for sigue por donde lo dejó el next()
if p - anterior == 2:
yield (anterior, p)
anterior = p
Primero hace next(gen) a mano para tener el 2 como anterior. Después, el for p in gen sigue por donde lo dejó ese next(): recibe 3, 5, 7… y no vuelve a empezar, porque gen es un iterador (el mismo objeto), no una lista.
En el dibujo, el 149 recorre la tubería entera; el 151 ya no pasa de primos_hasta(150), que hace return y lo detiene todo.
Salida
Primeros 15: [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47] Hasta 100 hay 25 primos El primo número 50: 229 Entre 100 y 150: [101, 103, 107, 109, 113, 127, 131, 137, 139, 149] Primeras 5 parejas de gemelos: [(3, 5), (5, 7), (11, 13), (17, 19), (29, 31)]
Comprobación a mano: hasta 100 hay 25 primos (2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97). El primo 50 es 229: los 49 next() del bucle se tragan los 49 primeros y el siguiente es el que imprimimos. Entre 100 y 150 hay 10 (fíjate en que 119 = 7 × 17 no está, y 121 = 11 × 11 tampoco). Las cinco primeras parejas de gemelos: 3 y 5, 5 y 7 (el 5 está en dos parejas), 11 y 13, 17 y 19, 29 y 31.
Vuelta de gemelos | anterior | p recibido | ¿p - anterior == 2? | Entrega |
|---|---|---|---|---|
| (antes del for) | 2 | — | — | — |
| 1 | 2 | 3 | No (1) | — |
| 2 | 3 | 5 | Sí | (3, 5) |
| 3 | 5 | 7 | Sí | (5, 7) |
| 4 | 7 | 11 | No (4) | — |
| 5 | 11 | 13 | Sí | (11, 13) |
Preguntas que te puedes hacer
¿Por qué sum(1 for _ in primos_hasta(100)) y no len(...)? Porque un generador no tiene len(): no sabe cuántos elementos va a dar hasta que los da. La expresión generadora suma un 1 por cada primo sin crear ninguna lista. len(list(primos_hasta(100))) también vale, pero construye una lista solo para contarla.
¿Por qué n_primos compara contador == n antes del yield? Para no calcular ni un primo de más. Si comprobáramos después del yield, cuando n es 15 el generador calcularía el primo 16 antes de enterarse de que ya ha terminado.
¿Qué pasaría si primos_hasta usara break en vez de return? Lo mismo: después del for no hay nada, así que la función acabaría. Pero si mañana alguien añade código después del bucle, con break se ejecutaría y con return no. return dice exactamente lo que queremos: «este generador ya ha terminado».
¿Por qué [next(pares) for _ in range(5)] y no list(pares)? Porque hay infinitos primos gemelos (o eso se sospecha: es un problema abierto de las matemáticas). list(pares) no terminaría nunca.
Escribir primos_hasta comprobando el límite dentro de primos() con una variable global, o filtrar con [p for p in primos() if p <= 150]. Esa comprensión no termina nunca: el if filtra, pero no para el for. Para cortar un generador infinito hace falta un return, un break o islice.
Pruébalo tú
- Escribe
factores(n), un generador que entregue los factores primos denusandoprimos()(para 360: 2, 2, 2, 3, 3, 5). - Escribe
huecos(gen), que entregue la distancia entre cada primo y el siguiente, y busca el mayor hueco por debajo de 1000.
Programa 3: iterador para la cola de reservas (lista encadenada)
Cuando un libro de la biblioteca está prestado, los socios pueden reservarlo y se ponen en cola: el primero que reservó se lo lleva cuando vuelve. El programa guarda esa cola como una lista encadenada: cada socio va en un nodo que apunta al siguiente. Tiene que poder reservar (añadir al final), atender (sacar al primero), decir cuántos hay, enviar un aviso a cada uno con su puesto y responder a in, list() y str(). Lo que lo complica: la estructura no es una lista de Python, así que el for no sabe recorrerla… hasta que le damos un __iter__.
Las listas encadenadas son el tema siguiente a las pruebas en el temario de FP2. Aquí solo usamos lo imprescindible (un nodo con un dato y un enlace al siguiente); lo importante hoy es cómo se recorre con un generador.
La idea
Cada nodo tiene socio y siguiente. La cola guarda dos referencias: _primero (por donde se atiende) y _ultimo (por donde se reserva, para no tener que recorrer toda la cola para llegar al final). Para recorrerla, __iter__ es un generador que empieza en _primero, entrega el socio de cada nodo y salta al siguiente hasta encontrar None. Como cada llamada a __iter__ crea un generador nuevo, todo lo demás (avisos, __str__, in, list()) sale gratis y se puede repetir.
Antes de programar, piensa qué tiene que pasar con _ultimo cuando atiendes al único socio que quedaba en la cola.
El código
class ColaReservas:
"""Lista encadenada con los socios que esperan un libro, en orden de llegada."""
class _Nodo:
def __init__(self, socio):
self.socio = socio
self.siguiente = None # el enlace al nodo de detrás
def __init__(self, titulo):
self.titulo = titulo
self._primero = None
self._ultimo = None # guardamos el último para añadir sin recorrer
self._n = 0
def reservar(self, socio):
nodo = self._Nodo(socio)
if self._primero is None: # cola vacía: el nuevo es primero y último
self._primero = nodo
else:
self._ultimo.siguiente = nodo
self._ultimo = nodo
self._n += 1
def atender(self):
"""El libro ha vuelto: sale el primero de la cola."""
if self._primero is None:
raise IndexError('no hay reservas')
nodo = self._primero
self._primero = nodo.siguiente
if self._primero is None: # se ha vaciado: tampoco hay último
self._ultimo = None
self._n -= 1
return nodo.socio
def __len__(self):
return self._n
def __iter__(self): # generador: recorre los nodos sin tocarlos
nodo = self._primero
while nodo is not None:
yield nodo.socio
nodo = nodo.siguiente
def avisos(self):
for puesto, socio in enumerate(self, start=1):
if puesto == 1:
yield f'{puesto}. {socio}: te toca'
else:
yield f'{puesto}. {socio}: tienes {puesto - 1} delante'
def __str__(self):
return ' -> '.join(self) + ' -> None'
cola = ColaReservas('Python a fondo')
for socio in ['Laura', 'Pablo', 'Marta', 'Iván']:
cola.reservar(socio)
print(cola.titulo, '|', len(cola), 'reservas')
print(cola)
for aviso in cola.avisos():
print(' ', aviso)
print('¿Marta está?', 'Marta' in cola, '| ¿Sara está?', 'Sara' in cola)
print('Dos recorridos:', list(cola), list(cola))
print('Vuelve el libro: se lo lleva', cola.atender())
print(cola)
print('Tipo de iter(cola):', type(iter(cola)).__name__)
El corazón del programa son estos dos generadores:
def __iter__(self): # generador: recorre los nodos sin tocarlos
nodo = self._primero
while nodo is not None:
yield nodo.socio
nodo = nodo.siguiente
def avisos(self):
for puesto, socio in enumerate(self, start=1):
if puesto == 1:
yield f'{puesto}. {socio}: te toca'
else:
yield f'{puesto}. {socio}: tienes {puesto - 1} delante'
nodoes una variable local del generador, no un atributo de la cola. Cada recorrido tiene la suya, igual que los cursores separados de la teoría, y recorrer no modifica la cola.avisos()usaenumerate(self, start=1):enumeratepide a__iter__de uno en uno y le pone delante el puesto. Así sale «tienes 2 delante» para Marta sin contar nada a mano.__str__usa' -> '.join(self):joinacepta cualquier iterable de cadenas, y la cola ya lo es.
En el dibujo, el cursor coral nodo está en Pablo: es el segundo next() del recorrido. La cola no se ha enterado.
Salida
Python a fondo | 4 reservas Laura -> Pablo -> Marta -> Iván -> None 1. Laura: te toca 2. Pablo: tienes 1 delante 3. Marta: tienes 2 delante 4. Iván: tienes 3 delante ¿Marta está? True | ¿Sara está? False Dos recorridos: ['Laura', 'Pablo', 'Marta', 'Iván'] ['Laura', 'Pablo', 'Marta', 'Iván'] Vuelve el libro: se lo lleva Laura Pablo -> Marta -> Iván -> None Tipo de iter(cola): generator
Repasemos cada línea. Cuatro reservas en orden de llegada: Laura, Pablo, Marta, Iván. Los avisos numeran desde 1, y el puesto p tiene p - 1 personas delante (Iván, 4 − 1 = 3). 'Marta' in cola da True aunque ColaReservas no tiene __contains__: si no lo encuentra, Python recorre el iterable con __iter__ y compara uno a uno. Los dos list(cola) seguidos salen completos, porque cada uno pidió un generador nuevo. Al volver el libro, atender() saca a Laura y la cola empieza en Pablo.
| Paso del generador | nodo apunta a | Entrega | nodo pasa a |
|---|---|---|---|
| 1 | Laura | 'Laura' | Pablo |
| 2 | Pablo | 'Pablo' | Marta |
| 3 | Marta | 'Marta' | Iván |
| 4 | Iván | 'Iván' | None |
| 5 | None | (sale del while: StopIteration) | — |
Preguntas que te puedes hacer
¿Por qué guardar _ultimo si con _primero ya llego a todos? Para que reservar sea inmediato. Sin _ultimo, cada reserva tendría que recorrer la cola hasta el final (con 1000 socios, 1000 saltos por reserva).
¿Por qué atender pone _ultimo = None cuando la cola se vacía? Porque si no, _ultimo seguiría apuntando al nodo que acabamos de sacar, y la siguiente reserva lo enlazaría a ese nodo «fantasma» en lugar de convertirse en la primera.
¿Por qué __iter__ no devuelve self como el iterador de la teoría? Porque la cola es un contenedor, no un recorrido. Si devolviera self con un cursor dentro, print(cola) dentro de un bucle sobre la cola rompería el bucle, como las 3 parejas de la estantería.
¿Puedo atender a alguien mientras recorro la cola con un for? Mejor no. El generador sigue los enlaces en el momento en que pasa por cada nodo; si cambias la estructura a mitad del recorrido, puedes saltarte socios o ver a alguien que ya se fue. Recorre primero (por ejemplo, pendientes = list(cola)) y modifica después.
Escribir __iter__ con return nodo.socio dentro del while. La función deja de ser un generador y el for falla con TypeError: iter() returned non-iterator of type 'str', porque __iter__ tiene que devolver un iterador, no un dato.
Pruébalo tú
- Añade
cancelar(socio), que saque a un socio de cualquier puesto (tendrás que enlazar el nodo de delante con el de detrás). - Añade
al_reves(), un generador que recorra la cola del último al primero. Pista: como los nodos solo apuntan hacia delante, recorre una vez guardando en una lista y luego entrega del final al principio.
El patrón que debes llevarte
Los tres programas repiten tres formas de generador que vas a reconocer en cualquier ejercicio:
| Patrón | Esqueleto | Dónde lo usamos |
|---|---|---|
| Generador infinito | while True: yield valor; avanzar | fibonacci(), primos() |
| Generador que corta | for x in otro: if fin: return; yield x | hasta(), primos_hasta(), n_primos() |
__iter__ generador | nodo = primero; while nodo: yield nodo.dato; nodo = nodo.siguiente | ColaReservas |
Errores típicos en esta práctica
- Pedir
list()a un generador infinito: el programa se queda colgado sin dar error. Corta antes conhasta,isliceo unbreak. - Reutilizar el mismo generador para dos preguntas: la segunda empieza donde acabó la primera. Crea uno nuevo para cada recorrido.
- Escribir la actualización de Fibonacci en dos líneas:
anterior = actualseguido deactual = anterior + actualusa el valor ya cambiado. Usa la asignación múltiple. - Filtrar un generador infinito con una comprensión:
[p for p in primos() if p <= 150]no termina; elifno detiene elfor. - Guardar el cursor como atributo de un contenedor: el segundo recorrido rompe el primero. El cursor, en una variable local del generador.
- Olvidar
_ultimoal vaciar la cola: la siguiente reserva se pierde enlazada a un nodo que ya no está.
Míralo paso a paso en Python Tutor
En Python Tutor, con Python 3, pega este Fibonacci reducido y avanza paso a paso:
def fibonacci():
anterior, actual = 0, 1
while True:
yield anterior
anterior, actual = actual, anterior + actual
g = fibonacci()
a = next(g)
b = next(g)
c = next(g)
d = next(g)
e = next(g)
print(a, b, c, d, e)
Salida:
0 1 1 2 3
Qué observar:
- Tras
g = fibonacci()aparece un objeto generador, pero el marco defibonaccino se ha creado todavía. - En
a = next(g)el marco aparece conanterior = 0yactual = 1y se para en elyield:arecibe 0. - En cada
next()siguiente, mira cómo cambian a la vezanterioryactualen la línea de la asignación múltiple: (0, 1) → (1, 1) → (1, 2) → (2, 3). - Entre un
next()y otro, el marco del generador sigue ahí con sus dos variables: eso es lo que significa «pausado».
Resumen y siguiente paso
- Un generador infinito solo guarda su estado (dos números en Fibonacci) y entrega valores bajo demanda.
- Para cortarlo:
returnen otro generador,islice,next()contados o unforconbreak; nuncalist()sin límite. - Los generadores se encadenan como una tubería: cada valor la recorre entera antes de calcular el siguiente.
returndentro de un generador lo termina; elforque lo recorre acaba sin error.- Un
__iter__escrito como generador hace que un contenedor propio funcione confor,list(),in,joinyenumerate, y se pueda recorrer las veces que quieras. - Un generador no tiene
len(): cuenta consum(1 for _ in gen).
Checklist: ¿lo has practicado?
- ¿Has ejecutado los tres programas y comprobado las salidas?
- ¿Sabes explicar por qué
anterior, actual = actual, anterior + actualno necesita variable temporal? - ¿Puedes decir tres formas de cortar un generador infinito?
- ¿Entiendes por qué
'Marta' in colafunciona sin__contains__? - ¿Has intentado los «Pruébalo tú» sin mirar la teoría?
Si has respondido que sí a casi todo, estás listo para los ejercicios de iteradores y generadores, donde ya no te doy el código hecho. ¡Nos vemos allí!
Siguiente paso: ejerciciosIteradores y generadores en Python — ejercicios con múltiplos, una secuencia que se reinicia y números perfectos (y una multa que salió letra a letra)¿Te ha servido este artículo? Así puedes apoyar SergioLearns →




Qué es un sistema operativo — teoría con tu portátil en una tarde de estudio, una llamada a write() y la CPU que nunca descansa (y los tres programas que no se ejecutaban a la vez)