Muchos algoritmos necesitan comparar cada valor con el siguiente: calcular diferencias, detectar cambios, validar orden, medir distancias, localizar intervalos y analizar series temporales. itertools.pairwise() transforma un iterable en pares solapados: (a, b), después (b, c) y después (c, d), sin cargar toda la entrada en memoria.
Esta guía explica pairwise con listas, generadores y archivos, cálculos de deltas, transiciones, validación de orden, timestamps, iterables cortos, objetos mutables, alternativas asíncronas y la diferencia entre pares solapados y lotes no solapados.
Primer ejemplo
from itertools import pairwise
valores = [10, 15, 12, 20]
for anterior, actual in pairwise(valores):
print(anterior, actual)
El resultado es (10, 15), (15, 12) y (12, 20). Cada valor intermedio participa dos veces: como segundo componente de un par y primero del siguiente.
Entradas cortas
Un iterable vacío o con un solo elemento no produce pares ni genera error.
list(pairwise([])) # []
list(pairwise([42])) # []
list(pairwise([1, 2])) # [(1, 2)]
Si el dominio exige al menos dos observaciones, valida ese contrato por separado.
Procesamiento perezoso
def contador():
numero = 0
while True:
yield numero
numero += 1
pares = pairwise(contador())
print(next(pares))
print(next(pares))
El iterador conserva únicamente el valor anterior necesario. Puede procesar streams grandes o infinitos con memoria auxiliar constante.
Calcular diferencias
temperaturas = [20.0, 21.5, 19.0, 23.0]
deltas = [actual - anterior for anterior, actual in pairwise(temperaturas)]
El resultado tiene un elemento menos porque la primera observación no posee precedente.
Tasas de cambio
muestras = [(0.0, 10.0), (2.0, 14.0), (5.0, 20.0)]
tasas = []
for (t1, v1), (t2, v2) in pairwise(muestras):
tasas.append((v2 - v1) / (t2 - t1))
Valida timestamps duplicados para evitar división por cero y confirma que las muestras están ordenadas cronológicamente.
Detectar transiciones de estado
estados = ["nuevo", "nuevo", "pagado", "enviado", "enviado"]
transiciones = [
(antes, despues)
for antes, despues in pairwise(estados)
if antes != despues
]
Este patrón es útil en auditorías, máquinas de estado, workflows y recorridos de usuario. Combínalo con enumerate() cuando importe la posición.
Validar orden
def esta_ordenado(valores):
return all(a <= b for a, b in pairwise(valores))
all() devuelve verdadero para entradas vacías o unitarias porque ningún par viola la regla. Añade una validación de longitud si el dominio requiere varios valores.
Duplicados consecutivos
duplicados = [a for a, b in pairwise(valores) if a == b]
Esto encuentra solo repeticiones adyacentes. Para duplicados en cualquier posición, usa set, Counter, una restricción de base de datos o una estrategia de agrupación.
Huecos en secuencias
ids = [1, 2, 5, 6, 10]
huecos = [(a, b) for a, b in pairwise(ids) if b - a > 1]
La misma técnica sirve con fechas, números de secuencia y sensores. Define cómo clasificar valores repetidos y regresiones.
Distancias en una trayectoria
from math import hypot
puntos = [(0, 0), (3, 4), (6, 4)]
total = sum(
hypot(x2 - x1, y2 - y1)
for (x1, y1), (x2, y2) in pairwise(puntos)
)
La suma mide la longitud recorrida, no la distancia directa entre el inicio y el final.
Comparar líneas consecutivas
with open("eventos.log", encoding="utf-8") as archivo:
for linea_anterior, linea_actual in pairwise(archivo):
comparar(linea_anterior, linea_actual)
El archivo se consume incrementalmente. Evita guardar todos los pares si el objetivo es memoria constante.
Añadir posiciones
for indice, (anterior, actual) in enumerate(pairwise(valores), start=1):
print(indice - 1, indice, anterior, actual)
El índice representa naturalmente la posición del segundo elemento.
Pairwise frente a zip y slicing
En secuencias, código antiguo usaba:
pares = zip(valores, valores[1:])
Esta forma crea un slice y no se generaliza directamente a iteradores. Antes de pairwise, las implementaciones genéricas usaban tee(). La función estándar es más clara.
Implementación conceptual
def pairwise_manual(iterable):
iterator = iter(iterable)
try:
anterior = next(iterator)
except StopIteration:
return
for actual in iterator:
yield anterior, actual
anterior = actual
La función oficial sigue esta idea de una sola pasada y conserva una referencia anterior.
Pairwise frente a batched
from itertools import batched, pairwise
list(pairwise([1, 2, 3, 4]))
# [(1, 2), (2, 3), (3, 4)]
list(batched([1, 2, 3, 4], 2))
# [(1, 2), (3, 4)]
pairwise es una ventana de tamaño dos con paso uno. batched crea bloques no solapados.
Ventanas mayores
pairwise está especializado en dos valores. Para ventanas de tres o más, usa un recipe con deque:
from collections import deque
def sliding_window(iterable, n):
ventana = deque(maxlen=n)
for item in iterable:
ventana.append(item)
if len(ventana) == n:
yield tuple(ventana)
Series temporales irregulares
No supongas un intervalo fijo. Calcula la diferencia temporal para cada par. Maneja zonas horarias, registros fuera de orden, timestamps duplicados, ajustes de reloj y observaciones ausentes.
Valores ausentes
Cuando existe None, decide si el par se omite, se imputa o se marca como inválido.
for a, b in pairwise(valores):
if a is None or b is None:
continue
procesar(b - a)
Valores NaN
NaN se propaga en cálculos y no es igual a sí mismo. Detecta ese valor antes de validar monotonía o calcular cambios significativos.
Objetos mutables reutilizados
pairwise guarda una referencia al objeto anterior. Si un generador modifica y vuelve a producir la misma instancia, ambos componentes pueden mostrar el estado más reciente. Genera snapshots independientes cuando necesites historial.
Excepciones de la fuente
Si el iterador fuente genera una excepción, pairwise la propaga. La referencia previa se libera al descartar el iterador.
Pairwise asíncrono
La función estándar acepta iterables síncronos. Una fuente async necesita un helper:
async def async_pairwise(source):
iterator = aiter(source)
try:
anterior = await anext(iterator)
except StopAsyncIteration:
return
async for actual in iterator:
yield anterior, actual
anterior = actual
Encontrar el mayor cambio
mayor = max(
((abs(b - a), a, b) for a, b in pairwise(valores)),
default=None,
)
Usa default porque una entrada con menos de dos valores no produce candidatos.
Transiciones frente a grupos
pairwise ayuda a localizar fronteras. Si necesitas producir grupos completos de claves iguales, itertools.groupby() suele ser más directo. Usa pairwise cuando la transición sea el evento principal.
Errores comunes
- Esperar pares no solapados: usa batched.
- Olvidar que salen n-1 pares: el primer valor no tiene anterior.
- Reutilizar un iterador consumido: muchas fuentes son de una sola pasada.
- Ignorar timestamps duplicados: las tasas pueden dividir por cero.
- Confundir duplicados adyacentes con globales: pairwise solo ve vecinos.
- Reutilizar un objeto mutable: las referencias históricas pueden engañar.
Ejemplo completo: auditoría de eventos
from itertools import pairwise
ORDEN = {
"creado": 0,
"pagado": 1,
"preparado": 2,
"enviado": 3,
"entregado": 4,
}
def auditar(eventos):
problemas = []
for anterior, actual in pairwise(eventos):
if actual.timestamp < anterior.timestamp:
problemas.append("timestamp regresivo")
if ORDEN[actual.estado] < ORDEN[anterior.estado]:
problemas.append(
f"transición inválida: {anterior.estado} -> {actual.estado}"
)
return problemas
La función valida cronología y progresión de estado en una sola pasada y memoria constante.
Conclusión
itertools.pairwise() expresa claramente algoritmos basados en vecinos consecutivos. Es perezoso, funciona con cualquier iterable síncrono y elimina slices o recetas manuales con tee.
La documentación oficial de itertools.pairwise define la función. Úsala para deltas, transiciones, huecos y validaciones, recordando que los pares se solapan y las entradas cortas no producen resultados.







