Link Search Menu Expand Document

Iterar con zip en Python

La función zip() de Python viene incluida por defecto en el namespace, lo que significa que puede ser usada sin tener que importarse.

De acuerdo con la documentación oficial:

Returns an iterator of tuples, where the i-th tuple contains the i-th element from each of the argument sequences or iterables. The iterator stops when the shortest input iterable is exhausted.

Dicho de otra manera, si pasamos dos listas a zip como entrada, el resultado será una tupla donde cada elemento tendrá todos y cada uno de los elementos i-ésimos de las pasadas como entrada.

Veamos un ejemplo. Como podemos ver, el resultado tras aplicar zip es una lista con a[0]b[0] en el primer elemento y a[1]b[1] como segundo.

a = [1, 2]
b = ["Uno", "Dos"]
c = zip(a, b)

print(list(c))
# [(1, 'Uno'), (2, 'Dos')]

A priori puede parecer una función no muy relevante, pero es realmente útil combinada con un for para iterar dos listas en paralelo.

a = [1, 2]
b = ["Uno", "Dos"]
c = zip(a, b)

for numero, texto in zip(a, b):
    print("Número", numero, "Letra", texto)
    
# Número 1 Letra Uno
# Número 2 Letra Dos

zip() con n argumentos

Ya hemos visto el uso de zip con dos listas, pero dado que está definida como zip(*iterables), es posible pasar un número arbitrario de iterables como entrada.

Veamos un ejemplo con varias listas. Es importante notar que todas tienen la misma longitud, dos.

numeros = [1, 2]
espanol = ["Uno", "Dos"]
ingles = ["One", "Two"]
frances = ["Un", "Deux"]
c = zip(numeros, espanol, ingles, frances)

for n, e, i, f in zip(numeros, espanol, ingles, frances):
    print(n, e, i, f)
    
# 1 Uno One Un
# 2 Dos Two Deux

zip() con diferentes longitudes

También podemos usar zip usando iterables de diferentes longitudes. En este caso lo que pasará es que el iterador para cuando la lista más pequeña se acaba.

numeros = [1, 2, 3, 4, 5]
espanol = ["Uno", "Dos"]

for n, e in zip(numeros, espanol):
    print(n, e)

# 1 Uno
# 2 Dos

Resulta lógico que este sea el comportamiento, porque de no ser así y se continuara, no tendríamos valores para usar.

zip() con un argumento

Como cabría esperar, dado que zip está definido para un número arbitrario de parámetros de entrada, es posible también posible usar un único valor. El resultado son tuplas de un elemento.

numeros = [1, 2, 3, 4, 5]
zz = zip(numeros)
print(list(zz))

# [(1,), (2,), (3,), (4,), (5,)]

zip() con diccionarios

Hasta ahora nos hemos limitado a usar zip con listas, pero la función está definida para cualquier clase iterable. Por lo tanto podemos usarla también con diccionarios.

Si realizamos lo siguiente, a,b toman los valores de las key del diccionario. Tal vez algo no demasiado interesante.

esp = {'1': 'Uno', '2': 'Dos', '3': 'Tres'}
eng = {'1': 'One', '2': 'Two', '3': 'Three'}

for a, b in zip(esp, eng):
    print(a, b)

# 1 1
# 2 2
# 3 3

Sin embargo, si hacemos uso de la función items, podemos acceder al key y value de cada elemento.

esp = {'1': 'Uno', '2': 'Dos', '3': 'Tres'}
eng = {'1': 'One', '2': 'Two', '3': 'Three'}

for (k1, v1), (k2, v2) in zip(esp.items(), eng.items()):
    print(k1, v1, v2)

# 1 Uno One
# 2 Dos Two
# 3 Tres Three

Nótese que en este caso ambas key k1 y k2 son iguales.

Deshacer el zip()

Con un pequeño truco, es posible deshacer el zip en una sola línea de código. Supongamos que hemos usado zip para obtener c.

a = [1, 2, 3]
b = ["One", "Two", "Three"]
c = zip(a, b)

print(list(c))
# [(1, 'One'), (2, 'Two'), (3, 'Three')]

¿Es posible obtener a y b desde c? Sí, tan sencillo como:

c = [(1, 'One'), (2, 'Two'), (3, 'Three')]
a, b = zip(*c)

print(a)  # (1, 2, 3)
print(b)  # ('One', 'Two', 'Three')

Nótese el uso de *c, lo que es conocido como unpacking en Python.