La función zip() es una de las herramientas más útiles y subestimadas de Python. Permite combinar dos o más iterables en pares, devolviendo un iterador que agrupa los elementos de cada secuencia. Si alguna vez necesitaste recorrer dos listas al mismo tiempo o transponer columnas en filas, zip() es la solución ideal.

En esta guía completa aprenderás desde lo básico hasta técnicas avanzadas con ejemplos prácticos y probados. Cubriremos la sintaxis, los parámetros, el modo strict (novedad de Python 3.10), la descompresión con zip(*), la creación de diccionarios y mucho más.

¿Qué es la función zip() de Python?

La función zip() recibe uno o más iterables (listas, tuplas, cadenas, etc.) y devuelve un iterador de tuplas, donde cada tupla contiene un elemento de cada iterable original. El nombre zip viene de la metáfora del cierre relámpago: los dientes de ambos lados se ensamblan uno a uno.

Esta es la sintaxis básica:

zip(*iterables, strict=False)

El parámetro strict se introdujo en Python 3.10. Cuando está activado, lanza un ValueError si los iterables tienen longitudes diferentes. Por defecto, zip() se detiene en el iterable más corto.

Fuente: Documentación oficial de Python — zip()

Cómo usar zip() en la práctica

Empecemos con el ejemplo más simple: unir dos listas del mismo tamaño.

nombres = ['Ana', 'Bruno', 'Carlos']
edades = [25, 32, 28]

pares = list(zip(nombres, edades))
print(pares)
# [('Ana', 25), ('Bruno', 32), ('Carlos', 28)]

Cada tupla combina el nombre con la edad correspondiente. Nota que usamos list() para materializar el iterador; sin él, zip() devuelve un objeto zip que se puede consumir bajo demanda.

Para iterar directamente:

for nombre, edad in zip(nombres, edades):
    print(f'{nombre} tiene {edad} años')
# Ana tiene 25 años
# Bruno tiene 32 años
# Carlos tiene 28 años

Puedes pasar tantos iterables como quieras. Con tres listas:

productos = ['Ratón', 'Teclado', 'Monitor']
precios = [50, 120, 800]
cantidades = [10, 5, 3]

for producto, precio, cant in zip(productos, precios, cantidades):
    total = precio * cant
    print(f'{producto}: ${total:.2f}')
# Ratón: $500.00
# Teclado: $600.00
# Monitor: $2400.00

Fuente: Real Python — Using the Python zip() Function

Comportamiento con iterables de diferentes longitudes

Por defecto, zip() deja de combinar cuando el iterable más corto se agota. Esto se llama comportamiento de truncamiento.

a = [1, 2, 3, 4, 5]
b = ['a', 'b', 'c']

resultado = list(zip(a, b))
print(resultado)
# [(1, 'a'), (2, 'b'), (3, 'c')]

Los valores 4 y 5 de la lista a se ignoran porque b solo tiene 3 elementos.

Modo strict (Python 3.10+)

Si quieres garantizar que todos los iterables tengan la misma longitud, usa strict=True:

try:
    resultado = list(zip(a, b, strict=True))
except ValueError as e:
    print(e)  # zip() argument 2 is shorter than argument 1

Esto es útil para evitar errores silenciosos en tuberías de datos, especialmente cuando los datos provienen de fuentes externas.

Fuente: GeeksforGeeks — zip() in Python

Descomprimir con zip(*)

El operador * invierte el zip(): "descomprime" una lista de tuplas de vuelta a tuplas separadas. Este patrón es extremadamente común.

pares = [('Ana', 25), ('Bruno', 32), ('Carlos', 28)]
nombres, edades = zip(*pares)

print(nombres)  # ('Ana', 'Bruno', 'Carlos')
print(edades)   # (25, 32, 28)

En la práctica, esto equivale a transponer datos: las "columnas" se convierten en "filas" y viceversa.

datos = [(1, 'a', True), (2, 'b', False), (3, 'c', True)]
col1, col2, col3 = zip(*datos)

print(col1)  # (1, 2, 3)
print(col2)  # ('a', 'b', 'c')
print(col3)  # (True, False, True)

Este patrón se usa mucho en análisis de datos y manipulación de matrices.

Fuente: Stack Overflow — Transpose/Unzip in Python

Crear diccionarios con zip()

Una de las aplicaciones más comunes de zip() es construir diccionarios combinando llaves y valores:

llaves = ['nombre', 'edad', 'ciudad']
valores = ['Ana', 25, 'Madrid']

persona = dict(zip(llaves, valores))
print(persona)
# {'nombre': 'Ana', 'edad': 25, 'ciudad': 'Madrid'}

Puedes ir más allá y crear una lista de diccionarios fácilmente:

campos = ['nombre', 'edad', 'ciudad']
datos = [
    ['Ana', 25, 'Madrid'],
    ['Bruno', 32, 'Barcelona'],
    ['Carlos', 28, 'Valencia']
]

personas = [dict(zip(campos, registro)) for registro in datos]
print(personas)
# [
#   {'nombre': 'Ana', 'edad': 25, 'ciudad': 'Madrid'},
#   {'nombre': 'Bruno', 'edad': 32, 'ciudad': 'Barcelona'},
#   {'nombre': 'Carlos', 'edad': 28, 'ciudad': 'Valencia'}
# ]

Si quieres profundizar en listas y manipulación de datos, consulta nuestra guía completa sobre listas en Python. Para más información sobre diccionarios, visita la guía completa de diccionarios en Python.

Fuente: Programiz — Python zip() Built-in Function

Iteración paralela con zip() en bucles

El caso de uso más frecuente de zip() es iterar sobre múltiples secuencias simultáneamente, evitando el acceso basado en índices.

estudiantes = ['Ana', 'Bruno', 'Carlos', 'Daniel']
notas = [8.5, 7.2, 9.0, 6.8]
aprobados = [True, True, True, False]

for estudiante, nota, aprobado in zip(estudiantes, notas, aprobados):
    estado = 'aprobado' if aprobado else 'reprobado'
    print(f'{estudiante} sacó {nota} — {estado}')

Sin zip(), necesitarías range(len(estudiantes)) y acceder a cada lista por índice, lo cual es más verboso y propenso a errores.

Comparación con enumerate

Usa zip() cuando las listas son independientes y quieres emparejarlas. Usa enumerate() cuando necesitas el índice de los elementos de una sola lista.

# zip para pares de listas
for nombre, edad in zip(nombres, edades):
    ...

# enumerate para índice + elemento
for i, nombre in enumerate(nombres):
    ...

Fuente: W3Schools — Python zip() Function

zip() con cadenas, tuplas y otros iterables

zip() funciona con cualquier iterable, no solo listas. Las cadenas, tuplas, conjuntos y generadores son válidos.

# Con cadenas
letras = zip('ABC', '123')
print(list(letras))  # [('A', '1'), ('B', '2'), ('C', '3')]

# Con tuplas
t1 = (1, 2, 3)
t2 = (4, 5, 6)
print(list(zip(t1, t2)))  # [(1, 4), (2, 5), (3, 6)]

# Con generator expressions
pares_impares = zip(
    (x for x in range(0, 10, 2)),
    (x for x in range(1, 10, 2))
)
print(list(pares_impares))
# [(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]

Fuente: Tutorial de Python — Estructuras de Datos

zip() vacío y con un solo iterable

Si no pasas ningún argumento, zip() devuelve un iterador vacío:

vacio = list(zip())
print(vacio)  # []

Con un solo iterable, zip() devuelve tuplas de un elemento:

unico = list(zip(['a', 'b', 'c']))
print(unico)  # [('a',), ('b',), ('c',)]

Esto puede ser útil cuando quieres estandarizar la salida de una función que a veces recibe uno y a veces varios iterables.

zip() vs itertools.zip_longest()

Mientras que zip() trunca al iterable más corto, itertools.zip_longest() continúa hasta el más largo, rellenando los valores faltantes con un fillvalue (por defecto None).

from itertools import zip_longest

a = [1, 2, 3, 4]
b = ['a', 'b']

print(list(zip(a, b)))
# [(1, 'a'), (2, 'b')]

print(list(zip_longest(a, b)))
# [(1, 'a'), (2, 'b'), (3, None), (4, None)]

print(list(zip_longest(a, b, fillvalue='X')))
# [(1, 'a'), (2, 'b'), (3, 'X'), (4, 'X')]

Elige zip() cuando quieres que los datos estén alineados perfectamente, y zip_longest() cuando necesitas preservar todos los elementos aunque algunas secuencias sean más cortas.

Fuente: Python itertools — zip_longest()

Ejemplos reales con zip()

1. Transposición de matriz

matriz = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

transpuesta = list(zip(*matriz))
print(transpuesta)
# [(1, 4, 7), (2, 5, 8), (3, 6, 9)]

2. Diccionario desde dos listas

estudiantes = ['Ana', 'Bruno', 'Carlos']
notas_finales = [9.0, 7.5, 8.8]

boletin = dict(zip(estudiantes, notas_finales))
print(boletin)  # {'Ana': 9.0, 'Bruno': 7.5, 'Carlos': 8.8}

3. Agrupar datos en pares consecutivos

def agrupar_en_pares(iterable):
    it = iter(iterable)
    return list(zip(it, it))

print(agrupar_en_pares([1, 2, 3, 4, 5, 6]))
# [(1, 2), (3, 4), (5, 6)]

4. Ordenar listas relacionadas juntas

nombres = ['Carlos', 'Ana', 'Bruno']
edades = [28, 25, 32]

# Ordenar edades siguiendo el orden de los nombres
pares_ordenados = sorted(zip(nombres, edades))
nombres_ordenados, edades_ordenadas = zip(*pares_ordenados)
print(list(nombres_ordenados))   # ['Ana', 'Bruno', 'Carlos']
print(list(edades_ordenadas))    # (25, 32, 28)

Fuente: NumPy — Guía para Principiantes

Rendimiento: zip() vs bucles con índices

zip() no solo es más legible — también es más eficiente. Comparemos el rendimiento con timeit:

import timeit

a = list(range(1000000))
b = list(range(1000000))

# Con zip()
tiempo_zip = timeit.timeit(
    '[(x, y) for x, y in zip(a, b)]',
    globals={'a': a, 'b': b},
    number=100
)

# Con índices
tiempo_indices = timeit.timeit(
    '[(a[i], b[i]) for i in range(len(a))]',
    globals={'a': a, 'b': b},
    number=100
)

print(f'zip(): {tiempo_zip:.3f}s')
print(f'índices: {tiempo_indices:.3f}s')

En la práctica, zip() suele ser más rápido porque delega la iteración a la implementación interna optimizada en C de Python, mientras que los bucles con índices implican más operaciones en la capa de Python.

Errores comunes con zip()

Olvidar materializar el iterador

z = zip([1, 2], ['a', 'b'])
print(z)  # <zip object at 0x...> — ¡no es lo que querías!
print(list(z))  # [(1, 'a'), (2, 'b')]

Confundir zip con zip_longest

Si no sabes que zip() trunca al iterable más corto, podrías perder datos silenciosamente. Siempre que la longitud de los iterables sea incierta, considera usar zip_longest() o validar con strict=True.

Modificar listas mientras se itera

a = [1, 2, 3]
b = ['a', 'b', 'c']

for x, y in zip(a, b):
    a.append(x * 10)  # ¡Esto crea un bucle infinito!

Itera siempre sobre copias o colecciones separadas si necesitas modificar la original.

Fuente: Real Python — zip() in Practice

Buenas prácticas

  • Prefiere zip() sobre índices: siempre que te sientas tentado a escribir for i in range(len(lista)) para acceder a dos listas, usa zip().
  • Usa desempaquetado: en lugar de for par in zip(a, b): x, y = par, escribe for x, y in zip(a, b) directamente.
  • Activa strict en validaciones: cuando los datos deben tener la misma longitud, pasa strict=True para evitar errores silenciosos.
  • Combínalo con sorted(): usa sorted(zip(...)) para ordenar múltiples listas de forma coordinada.
  • No abuses de zip(): para procesar una sola secuencia, enumerate() o un bucle simple son más adecuados.

Conclusión

La función zip() es una herramienta indispensable en el día a día de cualquier desarrollador Python. Simplifica la iteración paralela, la creación de diccionarios, la transposición de matrices y el emparejamiento de datos en general.

Dominar zip() — y saber cuándo usar zip_longest(), strict=True y la descompresión con * — es un paso importante para escribir código Python más idiomático, legible y eficiente.

Sigue explorando el Universo Python con nuestras guías gratuitas: aprende a trabajar con listas en Python y diccionarios en Python para complementar tu aprendizaje sobre manipulación de datos.