itertools.groupby() agrupa elementos consecutivos que producen la misma clave. Es lazy, consume la entrada solo cuando hace falta y puede procesar streams grandes sin cargar todos los datos en memoria. Su comportamiento no es igual al GROUP BY de SQL: claves iguales que no son adyacentes generan grupos separados.
Esta guía explica ordenación previa, subiteradores compartidos, agregaciones streaming, objetos, grupos anidados y errores frecuentes.
Ejemplo básico
from itertools import groupby
valores = [1, 1, 2, 2, 2, 3]
for clave, grupo in groupby(valores):
print(clave, list(grupo))
Cada secuencia consecutiva de valores iguales se convierte en un grupo.
La adyacencia es la regla principal
valores = [1, 2, 1]
print([(clave, list(grupo)) for clave, grupo in groupby(valores)])
El valor 1 aparece dos veces porque está separado. Ordena primero cuando quieras reunir todas las claves iguales.
Ordenar con la misma clave
registros.sort(key=lambda item: item["categoria"])
for categoria, grupo in groupby(registros, key=lambda item: item["categoria"]):
elementos = list(grupo)
procesar(categoria, elementos)
La clave de ordenación y la de agrupación deben representar la misma regla.
Los grupos comparten el iterador
Cada grupo es un subiterador conectado al iterador principal. Al avanzar a la siguiente clave, el grupo anterior deja de estar disponible. Materialízalo de inmediato si necesitas conservarlo.
for clave, grupo in groupby(datos, key=obtener_clave):
elementos = list(grupo)
guardar(clave, elementos)
Agregación streaming
for categoria, grupo in groupby(registros, key=lambda fila: fila.categoria):
total = sum(fila.importe for fila in grupo)
print(categoria, total)
El grupo se consume directamente y mantiene bajo el uso de memoria.
Contar sin crear listas
for clave, grupo in groupby(datos, key=obtener_clave):
cantidad = sum(1 for _ in grupo)
print(clave, cantidad)
Agrupar objetos
from operator import attrgetter
pedidos.sort(key=attrgetter("cliente_id"))
for cliente_id, grupo in groupby(pedidos, key=attrgetter("cliente_id")):
procesar_pedidos(cliente_id, grupo)
attrgetter puede expresar la intención mejor que varias lambdas repetidas.
Entradas ya ordenadas
Resultados SQL con ORDER BY, archivos ordenados y streams monotónicos son casos ideales. groupby permite agregarlos de forma incremental.
Diccionario frente a groupby
Un diccionario de listas agrupa sin importar el orden, pero conserva todos los elementos. groupby usa poca memoria, aunque necesita claves adyacentes.
Normalización de texto
nombres.sort(key=str.casefold)
for clave, grupo in groupby(nombres, key=str.casefold):
print(clave, list(grupo))
Usa la misma normalización al ordenar y agrupar. Para texto humano puede ser necesario considerar locale.
Agrupación anidada
filas.sort(key=lambda fila: (fila.pais, fila.ciudad))
for pais, grupo_pais in groupby(filas, key=lambda fila: fila.pais):
filas_pais = list(grupo_pais)
for ciudad, grupo_ciudad in groupby(filas_pais, key=lambda fila: fila.ciudad):
procesar(pais, ciudad, grupo_ciudad)
El grupo exterior se materializa porque todos los subgrupos comparten el iterador subyacente.
Errores comunes
- Esperar que claves iguales no adyacentes se unan.
- Ordenar y agrupar con claves incompatibles.
- Guardar el subiterador para consumirlo después.
- Convertir todos los grupos en listas sin necesidad.
- Usar una función de clave con efectos secundarios.
Buenas prácticas
Documenta el requisito de orden, mantén las funciones de clave puras, consume cada grupo antes de avanzar y materializa solo cuando sea necesario. Consulta también las guías internas de itertools en Python y itertools.pairwise.
Conclusión
itertools.groupby es ideal para segmentar secuencias ordenadas y crear agregaciones de bajo consumo de memoria. Comprender la adyacencia y los iteradores compartidos evita resultados incorrectos.







