Skip to content

Python itertools: Guía Completa de Bloques de Construcción de Iteradores

Updated on

Escribir bucles es algo natural para todo desarrollador Python. Pero al observar más de cerca la mayoría de las bases de código, encontrarás los mismos patrones apareciendo una y otra vez: aplanar listas anidadas, generar combinaciones, agrupar datos ordenados, dividir iteradores, acumular totales parciales. Cada uno se resuelve típicamente con un bucle for escrito a mano, variables temporales y malabarismo con índices. El código funciona, pero es verboso, propenso a errores y lento cuando los conjuntos de datos crecen.

Estos problemas se acumulan. Un bucle anidado sobre dos listas produce un producto cartesiano, pero escribirlo manualmente oscurece la intención. Generar todas las combinaciones de longitud 3 de una colección requiere una gestión cuidadosa de índices. Agrupar elementos consecutivos por una clave necesita una variable de estado que es fácil de equivocar. Cada uno de estos patrones ya ha sido resuelto, probado y optimizado en el módulo itertools de Python -- un conjunto de herramientas de la biblioteca estándar que convierte construcciones de bucles multilínea en llamadas a funciones individuales y legibles que producen iteradores eficientes en memoria.

Esta guía cubre cada función principal de itertools con ejemplos prácticos y ejecutables. Al final, tendrás una referencia para reemplazar patrones de bucles verbosos con pipelines de iteradores limpias y eficientes.

📚

Importar itertools

El módulo itertools es parte de la biblioteca estándar de Python. No se necesita instalación.

import itertools
 
# O importar funciones específicas
from itertools import chain, combinations, groupby, islice

Iteradores Infinitos

Estas funciones producen iteradores que nunca terminan por sí solos. Debes usar islice(), takewhile() u otro mecanismo de parada para evitar bucles infinitos.

count() -- Secuencias Aritméticas

count(start, step) genera valores uniformemente espaciados comenzando en start, incrementando en step (por defecto 1).

from itertools import count, islice
 
# Contar desde 10, paso 2
counter = count(10, 2)
print(list(islice(counter, 6)))
# [10, 12, 14, 16, 18, 20]
 
# Secuencias de punto flotante
floats = count(0.0, 0.25)
print(list(islice(floats, 5)))
# [0.0, 0.25, 0.5, 0.75, 1.0]
 
# Etiquetar filas con un índice
names = ["Alice", "Bob", "Charlie"]
for idx, name in zip(count(1), names):
    print(f"{idx}. {name}")
# 1. Alice
# 2. Bob
# 3. Charlie

cycle() -- Repetir una Secuencia Indefinidamente

cycle(iterable) guarda una copia del iterable y devuelve elementos de él repetidamente.

from itertools import cycle, islice
 
colors = cycle(["red", "green", "blue"])
print(list(islice(colors, 7)))
# ['red', 'green', 'blue', 'red', 'green', 'blue', 'red']
 
# Asignación round-robin
tasks = ["task_a", "task_b", "task_c", "task_d", "task_e"]
workers = cycle(["Worker1", "Worker2", "Worker3"])
assignments = {task: worker for task, worker in zip(tasks, workers)}
print(assignments)
# {'task_a': 'Worker1', 'task_b': 'Worker2', 'task_c': 'Worker3',
#  'task_d': 'Worker1', 'task_e': 'Worker2'}

repeat() -- Devolver el Mismo Valor

repeat(value, times) devuelve value ya sea infinitamente o un número fijo de veces.

from itertools import repeat
 
# Repeticiones fijas
print(list(repeat("hello", 3)))
# ['hello', 'hello', 'hello']
 
# Usar repeat como argumento constante en map()
import operator
bases = [2, 3, 4, 5]
squared = list(map(operator.pow, bases, repeat(2)))
print(squared)
# [4, 9, 16, 25]

Iteradores Finitos: División y Filtrado

Estas funciones consumen uno o más iterables de entrada y producen una salida finita.

chain() -- Concatenar Múltiples Iterables

chain(*iterables) devuelve elementos del primer iterable hasta que se agota, luego del siguiente, y así sucesivamente. chain.from_iterable() acepta un único iterable de iterables.

from itertools import chain
 
# Encadenar múltiples listas
a = [1, 2, 3]
b = [4, 5, 6]
c = [7, 8, 9]
print(list(chain(a, b, c)))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
 
# Aplanar un nivel de anidamiento
nested = [[1, 2], [3, 4], [5, 6]]
print(list(chain.from_iterable(nested)))
# [1, 2, 3, 4, 5, 6]
 
# Combinar un generador con una lista
def evens():
    yield 2; yield 4; yield 6
 
print(list(chain(evens(), [8, 10])))
# [2, 4, 6, 8, 10]

compress() -- Filtrar por Selectores

compress(data, selectors) devuelve elementos de data donde el elemento correspondiente en selectors es verdadero.

from itertools import compress
 
data = ["A", "B", "C", "D", "E"]
selectors = [1, 0, 1, 0, 1]
print(list(compress(data, selectors)))
# ['A', 'C', 'E']
 
# Usar con condiciones booleanas
values = [10, 25, 3, 42, 7, 18]
mask = [v > 15 for v in values]
print(list(compress(values, mask)))
# [25, 42, 18]

islice() -- Dividir Cualquier Iterador

islice(iterable, stop) o islice(iterable, start, stop, step) funciona como la notación de slice pero en cualquier iterador, sin construir una lista primero.

from itertools import islice, count
 
# Primeros 5 elementos de un contador infinito
print(list(islice(count(100), 5)))
# [100, 101, 102, 103, 104]
 
# Elementos del 3 al 8
print(list(islice(range(20), 3, 9)))
# [3, 4, 5, 6, 7, 8]
 
# Cada tercer elemento de los primeros 15
print(list(islice(range(100), 0, 15, 3)))
# [0, 3, 6, 9, 12]
 
# Leer las primeras 5 líneas de un iterador de archivo
# lines = list(islice(open("data.txt"), 5))

takewhile() y dropwhile() -- División Condicional

takewhile(predicate, iterable) devuelve elementos mientras el predicado sea verdadero, luego se detiene. dropwhile(predicate, iterable) omite elementos mientras el predicado sea verdadero, luego devuelve el resto.

from itertools import takewhile, dropwhile
 
data = [1, 3, 5, 7, 2, 4, 6, 8]
 
# Tomar elementos mientras sean menores que 6
print(list(takewhile(lambda x: x < 6, data)))
# [1, 3, 5]
 
# Descartar elementos mientras sean menores que 6
print(list(dropwhile(lambda x: x < 6, data)))
# [7, 2, 4, 6, 8]

Ten en cuenta que takewhile se detiene en el primer False; no se reanuda si elementos posteriores satisfacen el predicado nuevamente.

starmap() -- Aplicar una Función a Argumentos Pre-agrupados

starmap(function, iterable) aplica una función usando tuplas de argumentos del iterable. Esto es equivalente a function(*args) para cada args en el iterable.

from itertools import starmap
import operator
 
pairs = [(2, 5), (3, 4), (10, 3)]
print(list(starmap(operator.mul, pairs)))
# [10, 12, 30]
 
# Calcular hipotenusa para múltiples triángulos
import math
triangles = [(3, 4), (5, 12), (8, 15)]
print(list(starmap(math.hypot, triangles)))
# [5.0, 13.0, 17.0]

filterfalse() -- Filtro Inverso

filterfalse(predicate, iterable) devuelve elementos para los cuales el predicado retorna False. Es el complemento de la función integrada filter().

from itertools import filterfalse
 
numbers = range(10)
# Mantener números impares (donde "es_par" es falso)
odd = list(filterfalse(lambda x: x % 2 == 0, numbers))
print(odd)
# [1, 3, 5, 7, 9]

Iteradores Combinatorios

Estas funciones producen todas las disposiciones posibles de los elementos de entrada. Son fundamentales para búsqueda por fuerza bruta, pruebas y cálculos matemáticos.

product() -- Producto Cartesiano

itertools.product(*iterables, repeat=1) reemplaza bucles for anidados sobre múltiples secuencias.

from itertools import product
 
# Dos listas
colors = ["red", "blue"]
sizes = ["S", "M", "L"]
print(list(product(colors, sizes)))
# [('red', 'S'), ('red', 'M'), ('red', 'L'),
#  ('blue', 'S'), ('blue', 'M'), ('blue', 'L')]
 
# Equivalente a:
# [(c, s) for c in colors for s in sizes]
 
# Lanzamiento de dados: dos dados de seis caras
dice = range(1, 7)
all_rolls = list(product(dice, repeat=2))
print(f"Combinaciones totales: {len(all_rolls)}")
# Combinaciones totales: 36
print(all_rolls[:5])
# [(1, 1), (1, 2), (1, 3), (1, 4), (1, 5)]

permutations() -- Todas las Ordenaciones

permutations(iterable, r) genera todas las ordenaciones posibles de longitud r a partir de la entrada. El orden importa, por lo que tanto (A, B) como (B, A) se incluyen.

from itertools import permutations
 
# Todas las disposiciones de 2 letras de 'ABC'
print(list(permutations("ABC", 2)))
# [('A', 'B'), ('A', 'C'), ('B', 'A'),
#  ('B', 'C'), ('C', 'A'), ('C', 'B')]
 
# Permutaciones completas (todos los elementos)
print(list(permutations([1, 2, 3])))
# [(1, 2, 3), (1, 3, 2), (2, 1, 3),
#  (2, 3, 1), (3, 1, 2), (3, 2, 1)]
 
# Número de permutaciones: n! / (n-r)!
import math
n, r = 5, 3
print(f"P({n},{r}) = {math.perm(n, r)}")
# P(5,3) = 60

combinations() -- Subconjuntos Únicos

combinations(iterable, r) genera todos los subconjuntos únicos de longitud r. El orden no importa, por lo que (A, B) se incluye pero (B, A) no.

from itertools import combinations
 
# Todas las combinaciones de 2 elementos de [1, 2, 3, 4]
print(list(combinations([1, 2, 3, 4], 2)))
# [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]
 
# Elegir 3 ingredientes de un menú
toppings = ["cheese", "pepperoni", "mushrooms", "onions", "peppers"]
combos = list(combinations(toppings, 3))
print(f"Número de combinaciones de 3 ingredientes: {len(combos)}")
# Número de combinaciones de 3 ingredientes: 10
for c in combos[:3]:
    print(c)
# ('cheese', 'pepperoni', 'mushrooms')
# ('cheese', 'pepperoni', 'onions')
# ('cheese', 'pepperoni', 'peppers')

combinations_with_replacement() -- Subconjuntos con Repetición

Los elementos pueden seleccionarse más de una vez.

from itertools import combinations_with_replacement
 
# Denominaciones de monedas: elegir 3 monedas
coins = [1, 5, 10, 25]
selections = list(combinations_with_replacement(coins, 3))
print(f"Selecciones totales: {len(selections)}")
# Selecciones totales: 35
print(selections[:5])
# [(1, 1, 1), (1, 1, 5), (1, 1, 10), (1, 1, 25), (1, 5, 5)]

Agrupar Datos con groupby()

itertools.groupby(iterable, key=None) agrupa elementos consecutivos que comparten la misma clave. La entrada debe estar ordenada por la clave primero, de lo contrario claves idénticas en posiciones no adyacentes formarán grupos separados.

from itertools import groupby
 
# Agrupar elementos iguales consecutivos
data = "AAABBBCCAAB"
for key, group in groupby(data):
    print(f"{key}: {list(group)}")
# A: ['A', 'A', 'A']
# B: ['B', 'B', 'B']
# C: ['C', 'C']
# A: ['A', 'A']
# B: ['B']

Ejemplo Práctico: Agrupar Registros por Categoría

from itertools import groupby
from operator import itemgetter
 
sales = [
    {"product": "Widget", "category": "Hardware", "revenue": 150},
    {"product": "Gadget", "category": "Hardware", "revenue": 300},
    {"product": "App Pro", "category": "Software", "revenue": 500},
    {"product": "Cloud X", "category": "Software", "revenue": 200},
    {"product": "Cable",   "category": "Hardware", "revenue": 50},
]
 
# Ordenar por categoría primero -- groupby requiere entrada ordenada
sales.sort(key=itemgetter("category"))
 
for category, items in groupby(sales, key=itemgetter("category")):
    item_list = list(items)
    total = sum(item["revenue"] for item in item_list)
    print(f"{category}: {len(item_list)} productos, ${total} ingresos")
# Hardware: 3 productos, $500 ingresos
# Software: 2 productos, $700 ingresos

Agrupar Números por Propiedad

from itertools import groupby
 
numbers = sorted(range(1, 16), key=lambda x: x % 3)
for remainder, group in groupby(numbers, key=lambda x: x % 3):
    print(f"Resto {remainder}: {list(group)}")
# Resto 0: [3, 6, 9, 12, 15]
# Resto 1: [1, 4, 7, 10, 13]
# Resto 2: [2, 5, 8, 11, 14]

Totales Acumulados con accumulate()

itertools.accumulate(iterable, func=operator.add, initial=None) produce resultados acumulados (parciales). Por defecto calcula una suma acumulada, pero se puede suministrar cualquier función binaria.

from itertools import accumulate
import operator
 
# Suma acumulada
data = [1, 2, 3, 4, 5]
print(list(accumulate(data)))
# [1, 3, 6, 10, 15]
 
# Producto acumulado
print(list(accumulate(data, operator.mul)))
# [1, 2, 6, 24, 120]
 
# Máximo acumulado
temps = [72, 68, 75, 71, 78, 74, 80]
print(list(accumulate(temps, max)))
# [72, 72, 75, 75, 78, 78, 80]
 
# Con un valor inicial
print(list(accumulate(data, operator.add, initial=100)))
# [100, 101, 103, 106, 110, 115]

Práctico: Saldo Acumulado

from itertools import accumulate
 
transactions = [1000, -200, -150, 500, -300, -100, 250]
balances = list(accumulate(transactions))
print("Transacciones:", transactions)
print("Saldos:       ", balances)
# Transacciones: [1000, -200, -150, 500, -300, -100, 250]
# Saldos:        [1000, 800, 650, 1150, 850, 750, 1000]

Referencia Completa de Funciones

FunciónCategoríaDescripciónSalida de Ejemplo
count(start, step)InfinitoSecuencia aritmética10, 12, 14, 16, ...
cycle(iterable)InfinitoRepite iterable sin finA, B, C, A, B, C, ...
repeat(val, n)InfinitoMismo valor n veces (o infinito)5, 5, 5, 5
chain(*iterables)FinitoConcatenar iterables[1,2] + [3,4] -> 1,2,3,4
compress(data, sel)FinitoFiltrar por selectores booleanosABCDE, 10101 -> A,C,E
islice(iter, start, stop, step)FinitoDividir cualquier iteradorComo list[start:stop:step]
takewhile(pred, iter)FinitoDevolver mientras predicado sea verdaderoSe detiene en primer falso
dropwhile(pred, iter)FinitoOmitir mientras predicado sea verdaderoComienza en primer falso
filterfalse(pred, iter)FinitoDevolver donde predicado es falsoInverso de filter()
starmap(func, iter)FinitoAplicar función a tuplas de argumentosfunc(*args) para cada uno
accumulate(iter, func)FinitoTotales acumulados[1,3,6,10,15]
groupby(iter, key)FinitoAgrupar consecutivos por claveRequiere entrada ordenada
product(*iters)CombinatorioProducto cartesianoReemplaza bucles anidados
permutations(iter, r)CombinatorioTodas las ordenaciones de longitud rEl orden importa
combinations(iter, r)CombinatorioTodos los subconjuntos de longitud rEl orden no importa
combinations_with_replacement(iter, r)CombinatorioSubconjuntos con repeticiónLos elementos pueden repetirse

Rendimiento: itertools vs Bucles Manuales

Las funciones de itertools están implementadas en C, lo que las hace significativamente más rápidas y eficientes en memoria que los bucles Python equivalentes. Producen iteradores (evaluación perezosa), lo que significa que devuelven un elemento a la vez en lugar de construir listas enteras en memoria.

import time
from itertools import chain
 
# Benchmark: aplanar una lista de 1000 sublistas, cada una con 1000 elementos
nested = [list(range(1000)) for _ in range(1000)]
 
# Enfoque manual
start = time.perf_counter()
result_manual = []
for sublist in nested:
    result_manual.extend(sublist)
manual_time = time.perf_counter() - start
 
# Enfoque itertools
start = time.perf_counter()
result_itertools = list(chain.from_iterable(nested))
itertools_time = time.perf_counter() - start
 
print(f"Extend manual: {manual_time:.4f}s")
print(f"chain.from_iterable: {itertools_time:.4f}s")
print(f"Aceleración: {manual_time / itertools_time:.2f}x")
# Salida típica:
# Extend manual: 0.0180s
# chain.from_iterable: 0.0120s
# Aceleración: 1.50x

Eficiencia de Memoria

import sys
from itertools import islice, count
 
# Una lista de 1 millón de enteros
big_list = list(range(1_000_000))
print(f"Memoria de lista: {sys.getsizeof(big_list):,} bytes")
# Memoria de lista: 8,000,056 bytes
 
# Un iterador sobre el mismo rango (memoria despreciable)
big_iter = islice(count(), 1_000_000)
print(f"Memoria de iterador: {sys.getsizeof(big_iter)} bytes")
# Memoria de iterador: 72 bytes

Los iteradores procesan un elemento a la vez. Al encadenar múltiples transformaciones, no se crean listas intermedias.

Recetas Comunes

La documentación de itertools incluye varias "recetas" -- patrones comunes construidos a partir de las primitivas del módulo. Aquí están las más útiles.

Aplanar Listas Anidadas

from itertools import chain
 
def flatten(nested_list):
    """Aplanar un nivel de anidamiento."""
    return list(chain.from_iterable(nested_list))
 
data = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]
print(flatten(data))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

Ventana Deslizante

from itertools import islice
from collections import deque
 
def sliding_window(iterable, n):
    """Devolver una ventana deslizante de tamaño n sobre el iterable."""
    iterator = iter(iterable)
    window = deque(islice(iterator, n), maxlen=n)
    if len(window) == n:
        yield tuple(window)
    for item in iterator:
        window.append(item)
        yield tuple(window)
 
data = [1, 2, 3, 4, 5, 6, 7]
for window in sliding_window(data, 3):
    print(window)
# (1, 2, 3)
# (2, 3, 4)
# (3, 4, 5)
# (4, 5, 6)
# (5, 6, 7)

Nota: Python 3.12+ incluye itertools.pairwise() para ventanas de tamaño 2, e itertools.batched() para bloques sin superposición.

Dividir un Iterable en Bloques

from itertools import islice
 
def chunked(iterable, size):
    """Dividir un iterable en bloques de tamaño fijo."""
    iterator = iter(iterable)
    while True:
        chunk = list(islice(iterator, size))
        if not chunk:
            break
        yield chunk
 
data = list(range(1, 12))
for chunk in chunked(data, 3):
    print(chunk)
# [1, 2, 3]
# [4, 5, 6]
# [7, 8, 9]
# [10, 11]

En Python 3.12+, usa itertools.batched(iterable, n) para el mismo resultado con una función integrada.

Iteración por Pares

from itertools import pairwise  # Python 3.10+
 
data = [10, 20, 30, 40, 50]
for a, b in pairwise(data):
    print(f"{a} -> {b}, diferencia = {b - a}")
# 10 -> 20, diferencia = 10
# 20 -> 30, diferencia = 10
# 30 -> 40, diferencia = 10
# 40 -> 50, diferencia = 10

Round-Robin desde Múltiples Iterables

from itertools import cycle, islice
 
def roundrobin(*iterables):
    """Devolver elementos de cada iterable por turnos."""
    iterators = [iter(it) for it in iterables]
    active = len(iterators)
    nexts = cycle(iter(it).__next__ for it in iterables)
    # Enfoque más simple:
    pending = len(iterables)
    iters = cycle(iter(it) for it in iterables)
    # Usar la receta de la documentación:
    result = []
    iterators = list(map(iter, iterables))
    while iterators:
        next_iterators = []
        for it in iterators:
            try:
                result.append(next(it))
                next_iterators.append(it)
            except StopIteration:
                pass
        iterators = next_iterators
    return result
 
print(roundrobin("ABC", "D", "EF"))
# ['A', 'D', 'E', 'B', 'F', 'C']

Elementos Únicos Preservando el Orden

from itertools import filterfalse
 
def unique_everseen(iterable):
    """Devolver elementos únicos, preservando el orden de primera aparición."""
    seen = set()
    for element in filterfalse(seen.__contains__, iterable):
        seen.add(element)
        yield element
 
data = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
print(list(unique_everseen(data)))
# [3, 1, 4, 5, 9, 2, 6]

Encadenar Funciones itertools

El verdadero poder de itertools surge cuando compones múltiples funciones en un pipeline. Como cada función devuelve un iterador, no se desperdicia memoria entre etapas.

from itertools import chain, compress, accumulate, islice
 
# Pipeline: aplanar -> filtrar -> suma acumulada -> tomar los primeros 5
nested = [[10, 20, 30], [5, 15, 25], [40, 50]]
mask = [True, False, True, True, False, True, True, False]
 
flat = chain.from_iterable(nested)    # 10, 20, 30, 5, 15, 25, 40, 50
filtered = compress(flat, mask)        # 10, 30, 5, 25, 40
running = accumulate(filtered)         # 10, 40, 45, 70, 110
first_four = list(islice(running, 4))
 
print(first_four)
# [10, 40, 45, 70]

No se creó ninguna lista intermedia en ningún paso. Cada valor fluye a través del pipeline uno a la vez.

Experimentar con itertools en Jupyter

Los pipelines de iteradores pueden ser difíciles de depurar porque los iteradores se consumen en la primera pasada. Un entorno de notebook interactivo facilita inspeccionar resultados intermedios, probar casos límite y visualizar cómo fluyen los datos a través de cada etapa. RunCell (opens in a new tab) proporciona un entorno Jupyter con IA que es ideal para este tipo de exploración -- puedes recorrer paso a paso las salidas de iteradores, obtener explicaciones asistidas por IA cuando un pipeline se comporta de manera inesperada, y prototipar recetas rápidamente antes de llevarlas a código de producción.

FAQ

¿Qué es itertools en Python?

itertools es un módulo de la biblioteca estándar que proporciona una colección de funciones rápidas y eficientes en memoria para crear y trabajar con iteradores. Incluye herramientas para secuencias infinitas (count, cycle, repeat), patrones de iteración finitos (chain, islice, groupby) y combinatoria (product, permutations, combinations). Todas las funciones devuelven iteradores, lo que significa que generan valores de forma perezosa sin construir listas completas en memoria.

¿Cuál es la diferencia entre itertools.combinations e itertools.permutations?

combinations(iterable, r) genera todos los subconjuntos únicos de longitud r donde el orden no importa -- (A, B) y (B, A) se consideran iguales y solo se devuelve (A, B). permutations(iterable, r) genera todas las ordenaciones de longitud r donde el orden importa -- se devuelven tanto (A, B) como (B, A). Para n elementos eligiendo r, combinations produce n! / (r!(n-r)!) resultados mientras que permutations produce n! / (n-r)! resultados.

¿Cómo aplano una lista anidada con itertools?

Usa itertools.chain.from_iterable() para aplanar un nivel de anidamiento. Por ejemplo, list(chain.from_iterable([[1,2],[3,4],[5,6]])) devuelve [1, 2, 3, 4, 5, 6]. Para estructuras profundamente anidadas, necesitas un enfoque recursivo ya que chain.from_iterable solo elimina un nivel.

¿Por qué itertools.groupby requiere entrada ordenada?

groupby() agrupa elementos consecutivos que comparten la misma clave. No recorre todo el iterable para encontrar todos los elementos coincidentes. Si tus datos tienen [A, A, B, A], groupby produce tres grupos: A, B, A. Para obtener un solo grupo por cada clave, ordena los datos por la función clave antes de pasarlos a groupby.

¿Es itertools más rápido que los bucles normales de Python?

Sí. Las funciones de itertools están implementadas en C como parte de la biblioteca estándar de CPython, lo que las hace más rápidas que los bucles Python equivalentes escritos a mano. También usan evaluación perezosa (produciendo un elemento a la vez), lo que reduce el consumo de memoria. Para conjuntos de datos grandes, la combinación de velocidad C y cero listas intermedias puede proporcionar mejoras significativas de rendimiento.

Conclusión

El módulo itertools de Python reemplaza docenas de patrones de bucles comunes con llamadas a funciones individuales que son más rápidas, más legibles y más eficientes en memoria. Los iteradores infinitos (count, cycle, repeat) manejan secuencias que no tienen un final natural. Los iteradores finitos (chain, islice, groupby, accumulate, compress, takewhile, dropwhile) cubren filtrado, división y agregación. Las funciones combinatorias (product, permutations, combinations, combinations_with_replacement) eliminan bucles anidados para búsqueda exhaustiva.

El principio clave es la composición. Como cada función de itertools devuelve un iterador, puedes canalizar la salida de una directamente a otra sin almacenamiento intermedio. Esto permite procesar conjuntos de datos que no caben en memoria, un elemento a la vez, usando código limpio y declarativo.

📚