Blog

enumerate y zip en Python

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.

How useful was this post?

Click on a heart to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.