Generadores y yield
Las funciones generadoras usan yield para producir un flujo de valores de forma perezosa (lazy). Conservan el estado local entre pasos, lo que es ideal para archivos grandes, secuencias infinitas y pipelines ETL.
Receta
def squares(n: int):
for i in range(n):
yield i * i
for value in squares(5):
print(value)Cuándo usar esto:
- Leer archivos grandes línea por línea
- Producir secuencias ilimitadas (IDs, reintentos)
- Encadenar etapas de map/filter sin listas
- Implementar primitivas de multitarea cooperativa
Ejemplo de trabajo
from pathlib import Path
def read_lines(path: Path):
with path.open(encoding="utf-8") as fh:
for line in fh:
yield line.rstrip("\n")
def filter_nonempty(lines):
for line in lines:
if line.strip():
yield line
def parse_csv_rows(lines):
for line in lines:
yield line.split(",")
def pipeline(path: Path):
lines = read_lines(path)
nonempty = filter_nonempty(lines)
return parse_csv_rows(nonempty)
if __name__ == "__main__":
sample = Path("data.csv")
sample.write_text("a,b\n\n c,d\n", encoding="utf-8")
for row in pipeline(sample):
print(row)Lo que esto demuestra:
- Cada etapa es perezosa: no hay un archivo completo en memoria
- Los generadores se componen pasando el iterador a la siguiente función
- El manejador de archivos se cierra cuando el generador es recolectado por el garbage collector o se agota (con contexto en el generador)
- El pipeline devuelve un generador: el consumidor impulsa la ejecución
Profundización
Cómo funciona
- Objeto generador - Creado cuando se llama a la función generadora; el cuerpo aún no se ejecuta.
yieldsuspende - Guarda las variables locales del frame; se reanuda ennext().StopIteration- Se eleva cuando el generador retorna o se cae del final.yield from- Delega a un sub-iterador (ver artículo dedicado).- Estado del generador -
.gi_framecontiene el punto de ejecución (depuración).
Perfil de memoria
| Enfoque | Memoria |
|---|---|
list(process()) | O(n) todos los elementos |
| pipeline de generador | O(1) estado por etapa |
Notas de Python
# expresión generadora
total = sum(x * x for x in range(1_000_000))
# cerrar generador temprano
gen = squares(100)
next(gen)
gen.close() # eleva GeneratorExit dentroErrores comunes
- Generador agotado - El segundo bucle no hace nada. Solución: Recrear o materializar una vez.
- Fuga de recursos sin
withen el generador - Si el consumidor se detiene temprano, el archivo puede permanecer abierto hasta el GC. Solución:contextlib.closingotry/finallyen el generador. - Argumento predeterminado mutable en generador - Igual que en las funciones. Solución: Patrón centinela
None. - Orden de efectos secundarios - Perezoso significa que los efectos ocurren al consumir, no al definir. Solución: Documentar la semántica perezosa.
- Trazas de pila de depuración - Los frames suspendidos confunden a los recién llegados; usar
gen.send(None)para mayor claridad en la documentación.
Alternativas
| Alternativa | Usar cuando | No usar cuando |
|---|---|---|
| Comprensión de listas | n pequeño, se necesita reutilización | Streams grandes |
itertools | Operaciones de iterador estándar | Lógica de dominio personalizada por elemento |
| Generador asíncrono | Streams de E/S asíncronos | Lecturas de archivos síncronas |
| Iterador de fragmentos de pandas | Lotes tabulares | Análisis simple de líneas |
Preguntas frecuentes
¿generador vs lista?
Generador perezoso de un solo paso. Lista almacena todos los elementos para reutilización e indexación.
¿Qué es yield from?
Delega la iteración a otro iterable; aplana generadores anidados eficientemente.
¿Pueden los generadores devolver valores?
return value establece StopIteration.value (PEP 380); se consume a través de yield from, raramente se necesita solo.
¿propósito de send()?
Comunicación estilo corutina hacia el generador; avanzado; asyncio usa ideas similares.
¿Cómo detener la iteración temprano?
gen.close() activa GeneratorExit en el punto de yield; limpiar recursos en try/finally en el generador.
¿generator throw?
Inyecta una excepción en el punto de yield; patrones de prueba y corutinas.
¿typing Generator?
Generator[YieldType, SendType, ReturnType] para tipado avanzado; a menudo Iterator[T] es suficiente.
¿múltiples yield en un bucle?
Patrón normal; cada yield produce un paso del consumidor.
¿rendimiento vs lista?
Los generadores ahorran memoria; las listas pueden ser más rápidas para secuencias muy pequeñas debido a la sobrecarga.
¿tee iterator?
itertools.tee divide un iterador en dos iteradores cacheados; el costo de memoria aumenta.
Relacionado
- yield from y Sub-generadores - delegación
- Iteradores y el Protocolo Iterador - conceptos básicos del protocolo
- Gestores de Contexto - limpieza en generadores
- itertools - chain, islice
Versiones de Stack: Esta página fue escrita para Python 3.14.0 (estable 3.14, mantenimiento 3.13), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+, y uv 0.6+.