Python O Módulo itertools
Nas aulas Iteradores e Geradores, você viu que um iterador entrega um item por vez, sob encomenda. O módulo itertools ("ferramentas de iteração") é uma caixa de iteradores prontos para os trabalhos que aparecem sempre: numerar sem fim, revezar, emendar listas, agrupar, montar todas as combinações de um cardápio. Cada ferramenta substitui um laço que você escreveria na mão, e quase sempre com menos chance de errar.
countecycle: iteradores sem fim, e como limitá-los comzipeislice.chain: percorrer várias listas como se fossem uma.groupby: agrupar itens vizinhos, e por que os dados precisam estar ordenados.combinations,permutationseproduct: todas as duplas, pódios e combos.accumulate,pairwiseebatched: saldo acumulado, pares vizinhos e lotes.- Os erros reais: o
groupbyque repete grupos, o grupo que esvazia,not subscriptablee'int' object is not iterable.
A ideia: as máquinas da cozinha
A Ana poderia bater massa com um garfo, mas a padaria tem batedeira, liquidificador e máquina de fatiar. Cada máquina faz um serviço só, e faz bem. As ferramentas do itertools são assim: cada uma recebe iteráveis (listas, textos, geradores) e devolve um iterador que entrega o resultado aos poucos. Como iteradores, elas gastam pouca memória, e se gastam numa passada só.
| Ferramenta | O serviço | Exemplo da Ana |
|---|---|---|
count | Contar sem parar | Números de senha: 101, 102, 103… |
cycle | Repetir uma sequência em roda | Rodízio de quem abre a padaria |
chain | Emendar várias sequências | Os pedidos da manhã, da tarde e da noite |
groupby | Juntar vizinhos iguais | Total vendido de cada produto |
combinations, permutations, product | Montar combinações | Duplas de trabalho, pódio, combo lanche + suco |
Importe só o que for usar: from itertools import count, cycle.
1. count: contar sem fim
count(inicio) entrega inicio, inicio + 1, inicio + 2… para sempre. Sozinho, ele nunca para; junto com algo que tem fim, como o zip (aula enumerate() e zip()), ele para quando o outro acaba:
from itertools import count, islice
clientes = ["Ana", "Bruno", "Carla"]
for senha, nome in zip(count(101), clientes):
print(f"Senha {senha}: {nome}")
print(list(islice(count(10, 5), 4)))
print(count(1))
$ python3 contar.py Senha 101: Ana Senha 102: Bruno Senha 103: Carla [10, 15, 20, 25] count(1)
- zip(count(101), clientes)
- O
zipanda nos dois ao mesmo tempo e para quando o mais curto acaba. Os clientes acabam em 3; ocountnunca chega a dar o 104. - count(10, 5)
- Começa em 10 e anda de 5 em 5. O segundo argumento é o passo.
- islice(…, 4)
- "Fatia de iterador": só os 4 primeiros. É o jeito de cortar um iterador sem fim (a aula Geradores já o usou).
- print(count(1))
- Mostra só
count(1): o iterador não calculou nada ainda.
Para numerar uma lista que já existe, o enumerate(clientes, start=101) faz o mesmo. O count serve quando o que vai ser numerado não é uma lista pronta, ou quando você quer passos diferentes de 1.
2. cycle: em roda
cycle(sequencia) entrega os itens da sequência e, quando acaba, recomeça do primeiro, para sempre. O rodízio de quem abre a padaria:
from itertools import cycle
dias = ["seg", "ter", "qua", "qui", "sex"]
equipe = cycle(["Ana", "Bruno"])
for dia, quem in zip(dias, equipe):
print(dia, "-", quem, "abre a padaria")
$ python3 ciclo.py seg - Ana abre a padaria ter - Bruno abre a padaria qua - Ana abre a padaria qui - Bruno abre a padaria sex - Ana abre a padaria
Mais uma vez, quem dá o fim é o zip: os 5 dias. Sem o dias, um for quem in equipe: nunca terminaria.
count e cycle não têm fim
Nunca peça "tudo" deles: list(count()), sum(cycle(…)) ou um for sem break rodam até a memória acabar ou você apertar Ctrl+C. Sempre limite com zip, islice ou break.
3. chain: emendar sequências
chain(a, b, c) ("corrente") percorre a inteira, depois b, depois c, como se fossem uma lista só, mas sem criar a lista grande:
from itertools import chain
manha = ["pão", "leite"]
tarde = ["café", "bolo"]
noite = ["pizza"]
for item in chain(manha, tarde, noite):
print(item, end=" ")
print()
pedidos_por_mesa = [["suco", "pastel"], ["café"], ["coxinha", "refrigerante"]]
print(list(chain.from_iterable(pedidos_por_mesa)))
print(manha + tarde + noite)
$ python3 corrente.py pão leite café bolo pizza ['suco', 'pastel', 'café', 'coxinha', 'refrigerante'] ['pão', 'leite', 'café', 'bolo', 'pizza']
chain(manha, tarde, noite): as três listas em sequência. Oend=" "põe tudo na mesma linha (aula As Opções sep e end).chain.from_iterable(lista_de_listas): para quando as listas já estão dentro de uma lista. "Achata" um nível.manha + tarde + noitedá o mesmo resultado, mas monta uma lista nova com tudo. Ochainfunciona também com geradores e arquivos, que não aceitam+.
4. groupby: juntar vizinhos iguais
A padaria anotou as vendas do dia, uma por linha, e quer o total de cada produto. O groupby ("agrupar por") junta itens seguidos que têm a mesma chave:
from itertools import groupby
vendas = [("leite", 3), ("pão", 12), ("café", 1), ("pão", 8), ("leite", 2)]
vendas.sort(key=lambda venda: venda[0])
print(vendas)
for produto, grupo in groupby(vendas, key=lambda venda: venda[0]):
quantidades = [qtd for _, qtd in grupo]
print(f"{produto}: {quantidades} -> {sum(quantidades)}")
$ python3 agrupar.py
[('café', 1), ('leite', 3), ('leite', 2), ('pão', 12), ('pão', 8)]
café: [1] -> 1
leite: [3, 2] -> 5
pão: [12, 8] -> 20
- vendas.sort(key=…)
- Primeiro, ordena pela mesma chave que vai agrupar (aula Ordenando Listas). Agora as vendas de leite estão lado a lado, e as de pão também.
- key=lambda venda: venda[0]
- A chave do grupo: o nome do produto, posição 0 da tupla (aula lambda).
- for produto, grupo in groupby(…)
- A cada volta,
produtoé a chave egrupoé um iterador com os itens daquele grupo. - [qtd for _, qtd in grupo]
- Tira as quantidades do grupo. O
_é o nome do produto, que aqui não interessa.
A pegadinha: sem ordenar, os grupos se repetem
O groupby só olha o vizinho. Ele não procura a lista inteira: quando a chave muda, fecha o grupo e abre outro. Veja o mesmo programa sem o sort:
from itertools import groupby
vendas = [("leite", 3), ("pão", 12), ("café", 1), ("pão", 8), ("leite", 2)]
for produto, grupo in groupby(vendas, key=lambda venda: venda[0]):
quantidades = [qtd for _, qtd in grupo]
print(f"{produto}: {quantidades} -> {sum(quantidades)}")
$ python3 agrupar_errado.py leite: [3] -> 3 pão: [12] -> 12 café: [1] -> 1 pão: [8] -> 8 leite: [2] -> 2
Nenhum erro, nenhum aviso, e a resposta está errada: o pão aparece duas vezes, com 12 e com 8, em vez de 20. Cada vez que o produto mudou de uma linha para a outra, começou um grupo novo. É como uma pessoa que separa as fichas olhando só a de cima da pilha: se as fichas estão embaralhadas, ela faz pilhas repetidas.
Esse comportamento é útil quando você quer justamente as "corridas" de itens iguais, como numa contagem de letras repetidas em sequência:
from itertools import groupby
print([(letra, len(list(grupo))) for letra, grupo in groupby("aaabbcaa")])
$ python3 agrupar_letras.py
[('a', 3), ('b', 2), ('c', 1), ('a', 2)]
Os dois a do fim formam um grupo separado, porque não estão encostados nos três primeiros. Para somar todos os itens de cada chave, onde quer que estejam, ordene antes; ou use um defaultdict(int) ou um Counter, da aula O Módulo collections, que não precisam de ordem.
Missão: o programa deveria mostrar o total de cada produto, mas o pão e o leite aparecem duas vezes. Rode e veja. Conserte ordenando as vendas antes do groupby: acrescente vendas.sort() logo depois da lista. (Tuplas se ordenam pelo primeiro item, o nome do produto.)
from itertools import groupby
vendas = [("leite", 3), ("pão", 12), ("café", 1), ("pão", 8), ("leite", 2)]
for produto, grupo in groupby(vendas, key=lambda venda: venda[0]):
total = sum(qtd for _, qtd in grupo)
print(f"{produto}: {total}")
from itertools import groupby
vendas = [("leite", 3), ("pão", 12), ("café", 1), ("pão", 8), ("leite", 2)]
vendas.sort()
for produto, grupo in groupby(vendas, key=lambda venda: venda[0]):
total = sum(qtd for _, qtd in grupo)
print(f"{produto}: {total}")
5. Combinações: combinations, permutations e product
Três ferramentas montam "todas as possibilidades". A diferença entre elas é a pergunta: a ordem importa? e de onde vêm os itens?
from itertools import combinations, permutations
sabores = ["chocolate", "morango", "limão"]
print("Casquinha com 2 bolas (a ordem não importa):")
for dupla in combinations(sabores, 2):
print(" ", dupla)
print("Pódio do concurso de bolos (a ordem importa):")
for primeiro, segundo in permutations(["Ana", "Bruno", "Carla"], 2):
print(f" 1º {primeiro}, 2º {segundo}")
$ python3 combinar.py
Casquinha com 2 bolas (a ordem não importa):
('chocolate', 'morango')
('chocolate', 'limão')
('morango', 'limão')
Pódio do concurso de bolos (a ordem importa):
1º Ana, 2º Bruno
1º Ana, 2º Carla
1º Bruno, 2º Ana
1º Bruno, 2º Carla
1º Carla, 2º Ana
1º Carla, 2º Bruno
combinations(sabores, 2): grupos de 2, sem repetir e sem ligar para a ordem. Chocolate com morango é a mesma casquinha que morango com chocolate, então só aparece uma vez: 3 casquinhas.permutations(pessoas, 2): arrumações de 2 em que a ordem importa. Ana em 1º e Bruno em 2º é outro pódio que Bruno em 1º e Ana em 2º: 6 pódios.
Quantas são? O math (aula O Módulo math) tem as contas prontas, comb e perm, que dão o número sem montar a lista:
from itertools import combinations, permutations from math import comb, perm turma = ["Ana", "Bruno", "Carla", "Davi", "Eva"] print(len(list(combinations(turma, 2))), comb(5, 2)) print(len(list(permutations(turma, 2))), perm(5, 2)) print(len(list(permutations(turma))))
$ python3 contas.py 10 10 20 20 120
Sem o segundo argumento, permutations(turma) dá todas as filas possíveis com os 5: 120. Com 10 pessoas já seriam 3.628.800, e com 15, mais de um trilhão: essas listas crescem muito depressa, então pense antes de pedir list() delas.
product: um de cada
product(a, b) ("produto") combina um item de cada sequência, como dois for um dentro do outro (aula Laços Aninhados). O almoço do restaurante, prato + bebida:
from itertools import product
pratos = ["arroz e feijão", "macarrão"]
bebidas = ["suco", "refrigerante", "água"]
for prato, bebida in product(pratos, bebidas):
print(f"{prato} + {bebida}")
print(list(product([0, 1], repeat=3)))
$ python3 produto.py arroz e feijão + suco arroz e feijão + refrigerante arroz e feijão + água macarrão + suco macarrão + refrigerante macarrão + água [(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1), (1, 0, 0), (1, 0, 1), (1, 1, 0), (1, 1, 1)]
2 pratos × 3 bebidas = 6 almoços. O repeat=3 combina a mesma sequência com ela mesma três vezes: todas as respostas possíveis de três perguntas de sim (1) ou não (0).
| Pergunta | Ferramenta | Com "ABC", de 2 em 2 |
|---|---|---|
| Grupos, a ordem não importa | combinations(x, 2) | AB AC BC (3) |
| Arrumações, a ordem importa | permutations(x, 2) | AB AC BA BC CA CB (6) |
| Um de cada lista (pode repetir) | product(x, x) ou product(x, repeat=2) | AA AB AC BA BB BC CA CB CC (9) |
Missão: a lanchonete quer anunciar todos os combos lanche + suco. Use product(lanches, sucos), mostre cada combo como misto + suco de laranja e, no fim, Total: 6 combos. Dica: guarde em combos = list(product(…)) para poder percorrer e contar.
from itertools import product lanches = ["misto", "pastel", "coxinha"] sucos = ["laranja", "uva"] print(lanches) print(sucos)
from itertools import product
lanches = ["misto", "pastel", "coxinha"]
sucos = ["laranja", "uva"]
combos = list(product(lanches, sucos))
for lanche, suco in combos:
print(f"{lanche} + suco de {suco}")
print("Total:", len(combos), "combos")
6. Mais três úteis: accumulate, pairwise, batched
from itertools import accumulate, pairwise, batched
movimentos = [100, -30, -20, 50]
print(list(accumulate(movimentos)))
temperaturas = [21, 24, 23, 27]
for antes, depois in pairwise(temperaturas):
print(f"{antes} -> {depois}: {depois - antes:+}")
convidados = ["Ana", "Bruno", "Carla", "Davi", "Eva"]
for mesa in batched(convidados, 2):
print(mesa)
$ python3 outros.py
[100, 70, 50, 100]
21 -> 24: +3
24 -> 23: -1
23 -> 27: +4
('Ana', 'Bruno')
('Carla', 'Davi')
('Eva',)
- accumulate(movimentos)
- "Acumular": a soma até ali, item por item. É o saldo da conta depois de cada movimento: 100, 100 − 30 = 70, 70 − 20 = 50, 50 + 50 = 100.
- pairwise(temperaturas)
- "Aos pares": cada item com o seguinte, (21, 24), (24, 23), (23, 27). Bom para calcular diferenças. O
:+da f-string mostra o sinal sempre (aula Formatação). Existe desde o Python 3.10. - batched(convidados, 2)
- "Em lotes": tuplas de 2 em 2; o último lote fica com o que sobrou. São as mesas de dois lugares da festa. Existe desde o Python 3.12.
O batched chegou no Python 3.12 e o pairwise no 3.10. O Ubuntu 26.04 e o editor do guia têm o 3.14, e o Debian 13, o 3.13: todos têm os dois. Num Python mais velho, from itertools import batched dá ImportError.
Formas diferentes: itertools ou laço na mão?
| Tarefa | Laço na mão | Com itertools |
|---|---|---|
| Numerar a partir de 101 | senha = 101 … senha += 1 | zip(count(101), itens) (ou enumerate(itens, 101)) |
| Rodízio | itens[i % len(itens)] | cycle(itens) |
| Emendar listas | a + b + c | chain(a, b, c): serve também para geradores |
| Total por produto | defaultdict(int) num for | sort + groupby: útil quando os dados já vêm ordenados |
| Todos os pares | Dois for com range(i + 1, n) | combinations(itens, 2) |
| Um de cada lista | for dentro de for | product(a, b): um nível só, mesmo com 5 listas |
O laço na mão não está errado, e às vezes é mais fácil de ler para quem está começando. As ferramentas valem a pena quando o laço fica com índices e contas que podem sair errados por um.
Quando dá errado
groupby sem ordenar (sem erro nenhum)
É o agrupar_errado.py da seção 4: o pão apareceu duas vezes, pão: [12] -> 12 e pão: [8] -> 8. O groupby junta só vizinhos. Como consertar: ordene pela mesma chave antes de agrupar: dados.sort(key=chave) e groupby(dados, key=chave).
from itertools import groupby
alunos = ["Ana", "Alice", "Bruno", "Beatriz", "Caio"]
grupos = groupby(alunos, key=lambda nome: nome[0])
guardados = {}
for letra, grupo in grupos:
guardados[letra] = grupo
for letra, grupo in guardados.items():
print(letra, list(grupo))
$ python3 grupo_gasto.py A [] B [] C []
Os nomes sumiram! Cada grupo é um iterador que lê a lista junto com o groupby. Quando o for passou para o grupo seguinte, o anterior ficou para trás e foi gasto. Como consertar: transforme o grupo em lista na mesma volta em que ele aparece:
from itertools import groupby
alunos = ["Ana", "Alice", "Bruno", "Beatriz", "Caio"]
guardados = {}
for letra, grupo in groupby(alunos, key=lambda nome: nome[0]):
guardados[letra] = list(grupo)
print(guardados)
$ python3 grupo_ok.py
{'A': ['Ana', 'Alice'], 'B': ['Bruno', 'Beatriz'], 'C': ['Caio']}
[ ]
from itertools import count senhas = count(1) print(senhas[:5])
$ python3 erro_fatia.py
Traceback (most recent call last):
File "/home/ana/erro_fatia.py", line 4, in <module>
print(senhas[:5])
~~~~~~^^^^
TypeError: 'itertools.count' object is not subscriptable
"O objeto itertools.count não aceita índice nem fatia". Iteradores não guardam os itens, então não têm "do 0 ao 5". Como consertar: list(islice(senhas, 5)).
chain
from itertools import chain
total = chain(10, 20)
print("Criei a corrente.")
print(list(total))
$ python3 erro_chain.py
Criei a corrente.
Traceback (most recent call last):
File "/home/ana/erro_chain.py", line 5, in <module>
print(list(total))
~~~~^^^^^^^
TypeError: 'int' object is not iterable
"Um objeto int não é iterável". O chain emenda sequências, e 10 não é uma sequência. Repare que o erro só apareceu na linha 5, e não na 3: como todo iterador, o chain não faz nada até alguém pedir o primeiro item. Como consertar: passe listas, chain([10], [20]); para somar números, é só 10 + 20.
$ python3 print_obj.py <itertools.combinations object at 0x7f1893929bc0>
Um print(combinations(["a", "b", "c"], 2)) mostra o objeto, não as duplas: ele ainda não calculou nenhuma. Como consertar: print(list(combinations(…))), ou percorra com for.
Referência rápida
| Ferramenta | Entrega | Exemplo → resultado |
|---|---|---|
count(inicio, passo) | Números sem fim | count(10, 5) → 10 15 20 … |
cycle(seq) | A sequência em roda, sem fim | cycle("AB") → A B A B … |
islice(it, n) | Os n primeiros | islice(count(), 3) → 0 1 2 |
chain(a, b) | a e depois b | chain("ab", "cd") → a b c d |
chain.from_iterable(listas) | As listas de dentro, emendadas | [[1], [2, 3]] → 1 2 3 |
groupby(seq, key) | Pares (chave, grupo) de vizinhos iguais | "aab" → ('a', aa) ('b', b) |
combinations(seq, r) | Grupos de r, sem ordem | "ABC", 2 → AB AC BC |
permutations(seq, r) | Arrumações de r, com ordem | "AB" → AB BA |
product(a, b) / repeat=n | Um de cada | "AB", "12" → A1 A2 B1 B2 |
accumulate(seq) | Somas parciais | [1, 2, 3] → 1 3 6 |
pairwise(seq) | Pares de vizinhos (3.10+) | [1, 2, 3] → (1, 2) (2, 3) |
batched(seq, n) | Lotes de n (3.12+) | "ABCDE", 2 → AB CD E |
| Sintoma | Causa provável |
|---|---|
O groupby repete a mesma chave | Os dados não estavam ordenados pela chave |
| Os grupos guardados estão vazios | Guardou o iterador do grupo; use list(grupo) na mesma volta |
TypeError: 'itertools.…' object is not subscriptable | Usou [ ] num iterador: use islice |
TypeError: 'int' object is not iterable | Passou um número onde se espera uma sequência |
<itertools.… object at 0x…> | Mostrou o iterador: use list() |
| O programa não termina | count ou cycle sem zip, islice ou break |
Exercícios
1. Os 3 primeiros
Complete para mostrar [1, 2, 3]:
2. Quantas duplas?
Uma turma de 4 alunos. Quantos itens tem list(combinations(turma, 2))?
3. Um de cada
Qual ferramenta combina um item de cada lista, como dois for aninhados? Escreva só o nome.
4. [(k, len(list(g))) for k, g in groupby("aabaa")] dá:
5. Para sortear o 1º e o 2º lugar de uma rifa entre 10 pessoas, interessa a ordem. Qual ferramenta lista todas as possibilidades?
6. Por que for x in cycle(["a", "b"]): print(x) é perigoso?
Missão final: as duplas do trabalho
Missão: a professora quer ver todas as duplas possíveis para o trabalho em grupo (Ana com Bruno é a mesma dupla que Bruno com Ana). Use combinations(alunos, 2) para montar a lista duplas, mostre cada uma como Ana e Bruno e, no fim, o total.
from itertools import combinations
alunos = ["Ana", "Bruno", "Carla", "Davi"]
duplas = []
for a, b in duplas:
print(a, "e", b)
print("Total de duplas:", len(duplas))
from itertools import combinations
alunos = ["Ana", "Bruno", "Carla", "Davi"]
duplas = list(combinations(alunos, 2))
for a, b in duplas:
print(a, "e", b)
print("Total de duplas:", len(duplas))
São 6 duplas, de Ana e Bruno até Carla e Davi. Troque combinations por permutations e veja o total dobrar para 12: agora "Ana e Bruno" e "Bruno e Ana" contam separado.
Resumo
itertoolstraz iteradores prontos; todos entregam aos poucos e se gastam numa passada.countecyclenão têm fim: limite comzip,isliceoubreak.chainemenda sequências (inclusive geradores) sem montar uma lista grande.groupbyjunta só vizinhos: ordene antes pela mesma chave, e transforme cada grupo em lista na mesma volta.combinations(sem ordem),permutations(com ordem) eproduct(um de cada) montam todas as possibilidades; elas crescem depressa.accumulatedá somas parciais,pairwisedá vizinhos,batcheddá lotes.