Blog

enumerate y zip en Python

Dos funciones incorporadas que reemplazan el contador que sigues escribiendo a mano y la indexación que sigues equivocando. Una de las dos además tira datos a la basura sin avisarte.

Estos dos van primero porque los escribes todos los días, y porque lo último que hace zip en este post es justo la pregunta que responde el resto de la serie.

El contador que sigues escribiendo

names = ['ada', 'grace', 'alan']

i = 0
for name in names:
    print(i, name)
    i += 1

Imprime:

0 ada
1 grace
2 alan

Tres líneas de contabilidad alrededor de una línea de trabajo real. enumerate se encarga de todas:

names = ['ada', 'grace', 'alan']

for i, name in enumerate(names):
    print(i, name)

Imprime:

0 ada
1 grace
2 alan

enumerate te entrega un par en cada vuelta: el conteo y después el elemento.

start cambia el número, no la posición

names = ['ada', 'grace', 'alan']

for n, name in enumerate(names, start=1):
    print(f"{n}. {name}")

Imprime:

1. ada
2. grace
3. alan

Este es el que la gente busca y luego termina escribiendo i + 1 en su lugar. start no se salta nada: solo cambia el primer número que se reparte.

Deja de indexar a mano

names = ['ada', 'grace', 'alan']

for i in range(len(names)):          # you index on every line that uses the item
    print(i, names[i].upper())

for i, name in enumerate(names):     # the item is already unpacked
    print(i, name.upper())

Imprime:

0 ADA
1 GRACE
2 ALAN
0 ADA
1 GRACE
2 ALAN

La misma salida, y la segunda versión no puede equivocarse con el índice. Vale la pena tratar range(len(x)) como una pequeña alarma: casi siempre significa enumerate, y a veces significa que nunca necesitaste el índice.

zip recorre dos secuencias a la vez

names = ['ada', 'grace', 'alan']
langs = ['analytical engine', 'cobol', 'turing machine']

for name, lang in zip(names, langs):
    print(f"{name:6} {lang}")

Imprime:

ada    analytical engine
grace  cobol
alan   turing machine

zip se detiene en la entrada más corta y no dice nada

Este es el que le cuesta una tarde a mucha gente.

names  = ['ada', 'grace', 'alan']
scores = [90, 85]

pairs = list(zip(names, scores))
print(pairs)
print(len(names), len(scores), '->', len(pairs))

Imprime:

[('ada', 90), ('grace', 85)]
3 2 -> 2

Alan desapareció. Sin error, sin advertencia: solo dos filas donde esperabas tres. Si se supone que las dos listas tienen el mismo largo, dilo y deja que Python lo verifique:

names  = ['ada', 'grace', 'alan']
scores = [90, 85]

try:
    print(list(zip(names, scores, strict=True)))
except ValueError as err:
    print('ValueError:', err)

Imprime:

ValueError: zip() argument 2 is shorter than argument 1

strict=True necesita Python 3.10 o más nuevo. Úsalo siempre que los largos iguales sean una suposición y no una coincidencia.

Dos cosas que vale la pena saber

Un zip de claves y valores es un diccionario:

cols = ['id', 'name', 'role']
row  = [17, 'ada', 'engineer']

print(dict(zip(cols, row)))

Imprime:

{'id': 17, 'name': 'ada', 'role': 'engineer'}

Y zip(*pairs) deshace el zip, porque desempacar la lista de pares convierte cada par en un argumento separado:

pairs = [('ada', 90), ('grace', 85), ('alan', 71)]

names, scores = zip(*pairs)
print(names)
print(scores)
print(sum(scores) / len(scores))

Imprime:

('ada', 'grace', 'alan')
(90, 85, 71)
82.0

Te devuelve tuplas, no listas. Eso sorprende una vez.

Los dos juntos

names  = ['ada', 'grace', 'alan']
scores = [90, 85, 71]

for n, (name, score) in enumerate(zip(names, scores), start=1):
    print(f"{n}. {name:6} {score}")

Imprime:

1. ada    90
2. grace  85
3. alan   71

Los paréntesis alrededor de (name, score) son obligatorios. enumerate te da dos cosas, y la segunda es a su vez un par.

Ninguno de los dos te da una lista

names  = ['ada', 'grace', 'alan']
scores = [90, 85, 71]

z = zip(names, scores)
print(list(z))
print(list(z))

Imprime:

[('ada', 90), ('grace', 85), ('alan', 71)]
[]

El segundo list(z) está vacío. No se borró nada y nada falló: zip te devolvió algo que se puede recorrer una sola vez, y ya lo recorriste. enumerate, map, filter y un archivo abierto se comportan igual.

Eso no es una rareza de zip. Así funciona la iteración en todo Python, y es el tema del próximo post.

Qué recordar

  • enumerate(x, start=1) en lugar de un contador hecho a mano, y en lugar de range(len(x)).

  • zip recorta hasta la entrada más corta en silencio. Pasa strict=True cuando los largos iguales sean una suposición.

  • dict(zip(claves, valores)) arma un diccionario; zip(*pares) lo desarma, en tuplas.

  • Los dos devuelven algo que se recorre una sola vez. Envuélvelo en list() apenas necesites usarlo dos veces.

¿Qué tan útil te resultó este post?

¡Haz clic en un corazón para calificar!

Calificación promedio 0 / 5. Total de votos: 0

Todavía no hay votos. Sé el primero en calificar este post.