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, usazip(). - Usa desempaquetado: en lugar de
for par in zip(a, b): x, y = par, escribefor x, y in zip(a, b)directamente. - Activa strict en validaciones: cuando los datos deben tener la misma longitud, pasa
strict=Truepara 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.