A função zip() é uma das ferramentas mais úteis e subestimadas do Python. Ela permite combinar dois ou mais iteráveis em pares, criando um iterador que agrupa os elementos de cada sequência. Se você já precisou percorrer duas listas ao mesmo tempo ou transformar colunas em linhas, o zip() é a solução ideal.

Neste guia completo, você vai aprender desde o básico até técnicas avançadas com exemplos práticos e testados. Vamos cobrir a sintaxe, os parâmetros, o modo strict (novidade no Python 3.10), descompressão com zip(*), criação de dicionários e muito mais.

O que é a função zip() do Python?

A função zip() recebe um ou mais iteráveis (listas, tuplas, strings, etc.) e retorna um iterador de tuplas, onde cada tupla contém um elemento de cada iterável original. O nome zip vem da metáfora de um zíper: os dentes de ambos os lados se encaixam um a um.

Veja a sintaxe básica:

zip(*iterables, strict=False)

O parâmetro strict foi introduzido no Python 3.10 e, quando ativado, lança um ValueError se os iteráveis tiverem comprimentos diferentes. Por padrão, zip() para no menor iterável.

Fonte: Documentação oficial do Python — zip()

Como usar zip() na prática

Vamos começar com o exemplo mais simples: unir duas listas de mesmo tamanho.

nomes = ['Ana', 'Bruno', 'Carla']
idades = [25, 32, 28]

pares = list(zip(nomes, idades))
print(pares)
# [('Ana', 25), ('Bruno', 32), ('Carla', 28)]

Cada tupla combina o nome com a idade correspondente. Perceba que usamos list() para materializar o iterador; sem ele, zip() retorna um objeto zip que pode ser consumido sob demanda.

Para iterar diretamente:

for nome, idade in zip(nomes, idades):
    print(f'{nome} tem {idade} anos')
# Ana tem 25 anos
# Bruno tem 32 anos
# Carla tem 28 anos

Você pode passar quantos iteráveis quiser. Com três listas:

produtos = ['Mouse', 'Teclado', 'Monitor']
precos = [50, 120, 800]
quantidades = [10, 5, 3]

for produto, preco, qtd in zip(produtos, precos, quantidades):
    total = preco * qtd
    print(f'{produto}: R$ {total:.2f}')
# Mouse: R$ 500.00
# Teclado: R$ 600.00
# Monitor: R$ 2400.00

Fonte: Real Python — Using the Python zip() Function

Comportamento com iteráveis de tamanhos diferentes

Por padrão, zip() para de combinar quando o menor iterável termina. Isso é chamado de comportamento de truncamento.

a = [1, 2, 3, 4, 5]
b = ['a', 'b', 'c']

resultado = list(zip(a, b))
print(resultado)
# [(1, 'a'), (2, 'b'), (3, 'c')]

Os valores 4 e 5 da lista a são ignorados porque b só tem 3 elementos.

Modo strict (Python 3.10+)

Se você quer garantir que todos os iteráveis tenham o mesmo tamanho, use strict=True:

try:
    resultado = list(zip(a, b, strict=True))
except ValueError as e:
    print(e)  # zip() argument 2 is shorter than argument 1

Isso é útil para evitar bugs silenciosos em pipelines de dados, especialmente quando os dados vêm de fontes externas.

Fonte: GeeksforGeeks — zip() in Python

Descompactar com zip(*)

O operador * faz o inverso do zip(): ele "descompacta" uma lista de tuplas de volta em tuplas separadas. Esse padrão é extremamente comum.

pares = [('Ana', 25), ('Bruno', 32), ('Carla', 28)]
nomes, idades = zip(*pares)

print(nomes)   # ('Ana', 'Bruno', 'Carla')
print(idades)  # (25, 32, 28)

Na prática, isso equivale a transpor os dados: as "colunas" viram "linhas" e vice-versa.

dados = [(1, 'a', True), (2, 'b', False), (3, 'c', True)]
col1, col2, col3 = zip(*dados)

print(col1)  # (1, 2, 3)
print(col2)  # ('a', 'b', 'c')
print(col3)  # (True, False, True)

Esse padrão é muito usado em análises de dados e manipulação de matrizes.

Fonte: Stack Overflow — Transpose/Unzip in Python

Criar dicionários com zip()

Uma das aplicações mais comuns do zip() é criar dicionários combinando chaves e valores:

chaves = ['nome', 'idade', 'cidade']
valores = ['Ana', 25, 'São Paulo']

pessoa = dict(zip(chaves, valores))
print(pessoa)
# {'nome': 'Ana', 'idade': 25, 'cidade': 'São Paulo'}

Você pode ir além e criar uma lista de dicionários facilmente:

campos = ['nome', 'idade', 'cidade']
dados = [
    ['Ana', 25, 'São Paulo'],
    ['Bruno', 32, 'Rio de Janeiro'],
    ['Carla', 28, 'Belo Horizonte']
]

pessoas = [dict(zip(campos, registro)) for registro in dados]
print(pessoas)
# [
#   {'nome': 'Ana', 'idade': 25, 'cidade': 'São Paulo'},
#   {'nome': 'Bruno', 'idade': 32, 'cidade': 'Rio de Janeiro'},
#   {'nome': 'Carla', 'idade': 28, 'cidade': 'Belo Horizonte'}
# ]

Se você quer se aprofundar em listas e manipulação de dados, veja nosso guia completo sobre listas em Python. Para saber mais sobre dicionários, acesse o guia completo de dicionários em Python.

Fonte: Programiz — Python zip() Built-in Function

Iteração paralela com zip() em loops

O caso de uso mais frequente do zip() é iterar sobre múltiplas sequências ao mesmo tempo, evitando o uso de índices.

alunos = ['Ana', 'Bruno', 'Carla', 'Daniel']
notas = [8.5, 7.2, 9.0, 6.8]
aprovados = [True, True, True, False]

for aluno, nota, aprovado in zip(alunos, notas, aprovados):
    status = 'aprovado' if aprovado else 'reprovado'
    print(f'{aluno} tirou {nota} — {status}')

Sem o zip(), você precisaria usar range(len(alunos)) e acessar cada lista por índice, o que é mais verboso e propenso a erros.

Comparação com enumerate

Use zip() quando as listas são independentes e você quer emparelhá-las. Use enumerate() quando você precisa do índice dos elementos de uma única lista.

# zip para pares de listas
for nome, idade in zip(nomes, idades):
    ...

# enumerate para índice + elemento
for i, nome in enumerate(nomes):
    ...

Fonte: W3Schools — Python zip() Function

zip() com strings, tuplas e outros iteráveis

O zip() funciona com qualquer iterável, não apenas listas. Strings, tuplas, sets e generators são todos válidos.

# Com strings
letras = zip('ABC', '123')
print(list(letras))  # [('A', '1'), ('B', '2'), ('C', '3')]

# Com tuplas
t1 = (1, 2, 3)
t2 = (4, 5, 6)
print(list(zip(t1, t2)))  # [(1, 4), (2, 5), (3, 6)]

# Com generator expressions
pares_impares = zip(
    (x for x in range(0, 10, 2)),
    (x for x in range(1, 10, 2))
)
print(list(pares_impares))
# [(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]

Fonte: Python Tutorial — Data Structures

Trabalhando com zip vazio ou iterável único

Se você não passar nenhum argumento, zip() retorna um iterador vazio:

vazio = list(zip())
print(vazio)  # []

Com um único iterável, zip() retorna tuplas de um elemento:

unico = list(zip(['a', 'b', 'c']))
print(unico)  # [('a',), ('b',), ('c',)]

Isso pode ser útil quando você quer padronizar a saída de uma função que às vezes recebe um e às vezes vários iteráveis.

zip() vs itertools.zip_longest()

Enquanto o zip() padrão trunca no menor iterável, itertools.zip_longest() continua até o maior, preenchendo os valores faltantes com um fillvalue (padrão None).

from itertools import zip_longest

a = [1, 2, 3, 4]
b = ['a', 'b']

print(list(zip(a, b)))
# [(1, 'a'), (2, 'b')]

print(list(zip_longest(a, b)))
# [(1, 'a'), (2, 'b'), (3, None), (4, None)]

print(list(zip_longest(a, b, fillvalue='X')))
# [(1, 'a'), (2, 'b'), (3, 'X'), (4, 'X')]

Escolha zip() quando você quer que os dados estejam alinhados perfeitamente, e zip_longest() quando você quer preservar todos os elementos mesmo que algumas sequências sejam mais curtas.

Fonte: Python itertools — zip_longest()

Exemplos reais com zip()

1. Transposição de matriz

matriz = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

transposta = list(zip(*matriz))
print(transposta)
# [(1, 4, 7), (2, 5, 8), (3, 6, 9)]

2. Criar dicionário a partir de duas listas

alunos = ['Ana', 'Bruno', 'Carla']
notas_finais = [9.0, 7.5, 8.8]

boletim = dict(zip(alunos, notas_finais))
print(boletim)  # {'Ana': 9.0, 'Bruno': 7.5, 'Carla': 8.8}

3. Agrupar dados em pares consecutivos

def agrupar_em_pares(iteravel):
    it = iter(iteravel)
    return list(zip(it, it))

print(agrupar_em_pares([1, 2, 3, 4, 5, 6]))
# [(1, 2), (3, 4), (5, 6)]

4. Classe para ordenar listas relacionadas

nomes = ['Carla', 'Ana', 'Bruno']
idades = [28, 25, 32]

# Ordenar idades seguindo a ordenação dos nomes
pares_ordenados = sorted(zip(nomes, idades))
nomes_ordenados, idades_ordenadas = zip(*pares_ordenados)
print(list(nomes_ordenados))   # ['Ana', 'Bruno', 'Carla']
print(list(idades_ordenadas))  # (25, 32, 28)

Fonte: NumPy — Absolute Beginner's Guide

Performance: zip() vs índices

O zip() não é apenas mais legível — ele também é mais eficiente. Vamos comparar o desempenho com timeit:

import timeit

a = list(range(1000000))
b = list(range(1000000))

# Com zip()
tempo_zip = timeit.timeit(
    '[(x, y) for x, y in zip(a, b)]',
    globals={'a': a, 'b': b},
    number=100
)

# Com índices
tempo_indices = timeit.timeit(
    '[(a[i], b[i]) for i in range(len(a))]',
    globals={'a': a, 'b': b},
    number=100
)

print(f'zip(): {tempo_zip:.3f}s')
print(f'índices: {tempo_indices:.3f}s')

Na prática, zip() tende a ser mais rápido porque delega a iteração para a implementação interna do Python, que é otimizada em C, enquanto o loop com índices envolve mais operações na camada Python.

Erros comuns com zip()

Esquecer de materializar o iterador

z = zip([1, 2], ['a', 'b'])
print(z)  # <zip object at 0x...> — não é o que você quer!
print(list(z))  # [(1, 'a'), (2, 'b')]

Confundir zip com zip_longest

Se você não sabe que o zip() trunca no menor iterável, pode perder dados silenciosamente. Sempre que o tamanho dos iteráveis for incerto, considere usar zip_longest() ou validar com strict=True.

Modificar listas enquanto itera

a = [1, 2, 3]
b = ['a', 'b', 'c']

for x, y in zip(a, b):
    a.append(x * 10)  # Isso cria um loop infinito!

Sempre itere sobre cópias ou coleções separadas se precisar modificar a original.

Fonte: Real Python — zip() in Practice

Boas práticas

  • Preferir zip() a índices: sempre que você estiver tentado a escrever for i in range(len(lista)) para acessar duas listas, use zip().
  • Usar desempacotamento: em vez de for par in zip(a, b): x, y = par, faça for x, y in zip(a, b) diretamente.
  • Habilitar strict em validações: quando os dados devem ter o mesmo tamanho, passe strict=True para evitar bugs silenciosos.
  • Combinar com sorted(): use sorted(zip(...)) para ordenar múltiplas listas de forma coordenada.
  • Evitar zip() para tudo: para processar uma única sequência, enumerate() ou um loop simples são mais adequados.

Conclusão

A função zip() é uma ferramenta indispensável no dia a dia de qualquer desenvolvedor Python. Ela simplifica a iteração paralela, a criação de dicionários, a transposição de matrizes e o emparelhamento de dados de forma geral.

Dominar o zip() — e saber quando usar zip_longest(), strict=True e a descompressão com * — é um passo importante para escrever código Python mais idiomático, legível e eficiente.

Continue explorando o Universo Python com nossos guias gratuitos: veja como trabalhar com listas em Python e dicionários em Python para complementar seu aprendizado sobre manipulação de dados na linguagem.