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 derange(len(x)). -
O
zipcorta na entrada mais curta em silêncio. Passestrict=Truequando 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.