Blog

enumerate e zip em Python

Esses dois vêm primeiro porque você escreve os dois todo dia, e porque a última coisa que o zip faz neste post é a pergunta que o resto da série responde.

O contador que você escreve toda vez

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

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

Ele imprime:

0 ada
1 grace
2 alan

Três linhas de controle em volta de uma linha de trabalho. O enumerate faz tudo isso:

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

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

Ele imprime:

0 ada
1 grace
2 alan

O enumerate te entrega um par a cada volta: a contagem, depois o item.

start muda o número, não a posição

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

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

Ele imprime:

1. ada
2. grace
3. alan

Esse é o que as pessoas procuram e acabam escrevendo i + 1 no lugar. O start não pula nada — ele só muda o primeiro número entregue.

Pare de indexar na mão

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())

Ele imprime:

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

Mesma saída, e a segunda versão não tem como errar o índice. Vale tratar range(len(x)) como um pequeno alarme: quase sempre quer dizer enumerate, e às vezes quer dizer que você nem precisava do índice.

O zip percorre duas sequências juntas

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

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

Ele imprime:

ada    analytical engine
grace  cobol
alan   turing machine

O zip para na entrada mais curta e não diz nada

Esse é o que custa uma tarde.

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

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

Ele imprime:

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

O Alan sumiu. Sem erro, sem aviso, só duas linhas onde você esperava três. Se as duas listas deveriam ter o mesmo tamanho, diga isso e deixe o Python conferir:

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

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

Ele imprime:

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

O strict=True precisa do Python 3.10 ou mais novo. Use sempre que os tamanhos iguais forem uma suposição, e não uma coincidência.

Duas coisas que vale saber

Um zip de chaves e valores é um dict:

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

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

Ele imprime:

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

E zip(*pairs) desempacota, porque desempacotar a lista de pares transforma cada par num argumento separado:

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

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

Ele imprime:

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

Você recebe tuplas de volta, não listas. Isso surpreende uma vez.

Os dois 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}")

Ele imprime:

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

Os parênteses em volta de (name, score) são obrigatórios. O enumerate te dá duas coisas, e a segunda é ela mesma um par.

Nenhum dos dois te dá uma lista

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

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

Ele imprime:

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

O segundo list(z) vem vazio. Nada foi apagado e nada falhou — o zip devolveu algo que pode ser percorrido uma vez, e você percorreu. O enumerate, o map, o filter e um arquivo aberto se comportam do mesmo jeito.

Isso não é uma esquisitice do zip. É como a iteração funciona em todo lugar no Python, e é o próximo post.

O que lembrar

  • enumerate(x, start=1) no lugar de um contador feito na mão, e no lugar de range(len(x)).

  • O zip corta na entrada mais curta em silêncio. Passe strict=True quando tamanhos iguais forem uma suposição.

  • dict(zip(keys, values)) monta um dict; zip(*pairs) desmonta, em tuplas.

  • Os dois devolvem algo que você percorre uma vez. Envolva em list() no momento em que precisar duas vezes.

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.