Blog

enumerate e zip em Python

Dois builtins que substituem o contador que você escreve na mão e a indexação que você erra. Um deles também joga dados fora sem avisar.

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.

Quanto este post te ajudou?

Clique em um coração para avaliar!

Média das avaliações 0 / 5. Total de votos: 0

Nenhum voto até agora. Seja o primeiro a avaliar este post.