W3docs

Agrupamento de Listas em Python

Aprenda três formas de agrupar listas Python: defaultdict, itertools.groupby e dict comprehensions — com exemplos executáveis e armadilhas comuns.

Agrupar uma lista significa particionar seus elementos em sub-coleções que compartilham uma chave comum — por exemplo, agrupar palavras pela sua primeira letra, ou agrupar registros por um campo de categoria. Python oferece três abordagens principais: um loop manual com collections.defaultdict, itertools.groupby da biblioteca padrão e dict comprehensions. Este capítulo explica cada técnica, quando escolher uma em vez de outra e as armadilhas a evitar.

Capítulos relacionados: Python Lists · List Methods · List Comprehension · Loop Lists · collections Module

O que significa "agrupar"

Dado uma lista plana e uma função de chave que mapeia cada elemento para um rótulo de grupo, o objetivo é produzir um mapeamento de cada rótulo para a lista de elementos que pertencem a ele:

['apple', 'banana', 'avocado', 'blueberry', 'cherry', 'apricot']
  key = first letter
  →  {'a': ['apple', 'avocado', 'apricot'],
      'b': ['banana', 'blueberry'],
      'c': ['cherry']}

As três técnicas abaixo produzem esse tipo de resultado. Elas diferem em verbosidade, desempenho e nas restrições que impõem sobre a entrada.

Técnica 1: Loop manual com defaultdict

collections.defaultdict é a abordagem mais comum e flexível. Quando você acessa uma chave que ainda não existe, um defaultdict(list) cria automaticamente uma lista vazia para essa chave, de modo que você nunca precisa de uma verificação if key in d.

from collections import defaultdict

words = ['apple', 'banana', 'avocado', 'blueberry', 'cherry', 'apricot']

by_letter = defaultdict(list)
for word in words:
    by_letter[word[0]].append(word)

for letter, group in sorted(by_letter.items()):
    print(f'{letter}: {group}')
# a: ['apple', 'avocado', 'apricot']
# b: ['banana', 'blueberry']
# c: ['cherry']

Por que usar defaultdict em vez de um dict simples?

Com um dict simples você precisa de uma verificação explícita antes do primeiro append:

# Plain dict — more boilerplate, same result
by_letter = {}
for word in words:
    if word[0] not in by_letter:
        by_letter[word[0]] = []
    by_letter[word[0]].append(word)

Uma alternativa mais curta com um dict simples é dict.setdefault:

by_letter = {}
for word in words:
    by_letter.setdefault(word[0], []).append(word)

setdefault é adequado para scripts curtos, mas defaultdict é mais rápido (sem buscas de chave repetidas) e mais explícito quanto à intenção.

Agrupando por uma chave calculada

A chave pode ser qualquer expressão, não apenas um atributo. Aqui, uma lista de inteiros é dividida em grupos pares e ímpares:

from collections import defaultdict

numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]

by_parity = defaultdict(list)
for n in numbers:
    by_parity['even' if n % 2 == 0 else 'odd'].append(n)

print('even:', sorted(by_parity['even']))  # even: [2, 4, 6]
print('odd:', sorted(by_parity['odd']))    # odd: [1, 1, 3, 3, 5, 5, 5, 9]

Agrupando uma lista de dicts

Este é o cenário mais comum no mundo real — agrupar linhas de dados por um valor de campo:

from collections import defaultdict

data = [
    {'category': 'fruit', 'name': 'apple'},
    {'category': 'vegetable', 'name': 'carrot'},
    {'category': 'fruit', 'name': 'banana'},
    {'category': 'vegetable', 'name': 'broccoli'},
]

grouped = defaultdict(list)
for item in data:
    grouped[item['category']].append(item['name'])

for category, names in grouped.items():
    print(f'{category}: {names}')
# fruit: ['apple', 'banana']
# vegetable: ['carrot', 'broccoli']

Técnica 2: itertools.groupby

itertools.groupby agrupa elementos consecutivos que compartilham a mesma chave. É útil quando você precisa preservar a estrutura de comprimento de execução ou quando os dados já estão ordenados e você quer evitar construir todo o dicionário de uma vez (é lazy/streaming).

from itertools import groupby

words = ['apple', 'banana', 'avocado', 'blueberry', 'cherry', 'apricot']

# groupby only groups consecutive elements, so sort first
words_sorted = sorted(words, key=lambda w: w[0])

for letter, group in groupby(words_sorted, key=lambda w: w[0]):
    print(f'{letter}: {list(group)}')
# a: ['apple', 'avocado', 'apricot']
# b: ['banana', 'blueberry']
# c: ['cherry']

A armadilha crítica: ordene antes de groupby

groupby só agrupa itens consecutivos que compartilham a mesma chave. Se a entrada não estiver ordenada pela chave, você obtém vários grupos pequenos em vez de um grupo por chave:

from itertools import groupby

# Unsorted input — groupby produces WRONG results
numbers = [1, 1, 2, 3, 3, 1, 2, 2]
for key, group in groupby(numbers):
    print(f'{key}: {list(group)}')
# 1: [1, 1]   ← first run of 1s
# 2: [2]
# 3: [3, 3]
# 1: [1]      ← second run of 1s — NOT merged with the first!
# 2: [2, 2]

Sempre ordene pela mesma função de chave antes de chamar groupby:

numbers_sorted = sorted(numbers)
for key, group in groupby(numbers_sorted):
    print(f'{key}: {list(group)}')
# 1: [1, 1, 1]
# 2: [2, 2, 2]
# 3: [3, 3]

Quando groupby brilha: dados em streaming de grande volume

Como groupby retorna um iterador, ele não carrega todos os grupos na memória de uma vez. Isso o torna útil para processar grandes arquivos ordenados linha por linha sem construir um dicionário completo.

from itertools import groupby

# Grouping namedtuple records
from collections import namedtuple

Product = namedtuple('Product', ['category', 'name', 'price'])
products = [
    Product('dairy', 'milk', 1.10),
    Product('fruit', 'apple', 1.20),
    Product('fruit', 'banana', 0.50),
    Product('vegetable', 'broccoli', 1.50),
    Product('vegetable', 'carrot', 0.80),
]
# products is already sorted by category here

for category, group in groupby(products, key=lambda p: p.category):
    items = list(group)
    print(f'{category}: {[p.name for p in items]}')
# dairy: ['milk']
# fruit: ['apple', 'banana']
# vegetable: ['broccoli', 'carrot']

Técnica 3: Dict comprehension

Uma dict comprehension constrói o dict agrupado em uma única expressão. É concisa, mas tem uma desvantagem: a list comprehension interna re-examina toda a entrada para cada chave única, tornando-a O(n × k), onde k é o número de chaves únicas. Para listas pequenas isso é aceitável; para listas grandes, prefira defaultdict.

words = ['apple', 'banana', 'avocado', 'blueberry', 'cherry', 'apricot']

# Collect unique keys first, then build each group
letters = sorted(set(w[0] for w in words))
grouped = {letter: [w for w in words if w[0] == letter] for letter in letters}

for letter, group in grouped.items():
    print(f'{letter}: {group}')
# a: ['apple', 'avocado', 'apricot']
# b: ['banana', 'blueberry']
# c: ['cherry']

Esta técnica é mais legível quando o conjunto de chaves é pequeno e já conhecido — por exemplo, agrupando resultados True/False ou um conjunto fixo de categorias.

Agregando grupos após o agrupamento

Um acompanhamento comum ao agrupamento é a agregação: calcular uma soma, média, mínimo ou contagem por grupo. Combine defaultdict(list) com aritmética Python padrão:

from collections import defaultdict

scores = [
    ('Alice', 90), ('Bob', 75), ('Alice', 85),
    ('Bob', 88), ('Carol', 92),
]

by_student = defaultdict(list)
for name, score in scores:
    by_student[name].append(score)

for student, student_scores in sorted(by_student.items()):
    avg = sum(student_scores) / len(student_scores)
    print(f'{student}: scores={student_scores}, avg={avg:.1f}')
# Alice: scores=[90, 85], avg=87.5
# Bob: scores=[75, 88], avg=81.5
# Carol: scores=[92], avg=92.0

Escolhendo a técnica certa

SituaçãoMelhor escolha
Agrupamento geral, qualquer ordemdefaultdict(list)
Precisa processar dados ordenados em streamingitertools.groupby
Lista pequena, solução concisa de uma linhaDict comprehension
A entrada já está ordenadadefaultdict ou groupby
Precisa agregar (soma, média, etc.)defaultdict(list) + aritmética

Armadilhas comuns

Esquecer de ordenar antes de groupby. groupby só mescla chaves idênticas consecutivas. Sempre aplique sorted() na entrada pela mesma função de chave antes de passá-la para groupby.

Atribuir list(group) imediatamente. O iterador de grupo do groupby é esgotado assim que o for externo avança para a próxima chave. Converta-o para uma lista dentro do corpo do loop se precisar usá-lo mais de uma vez.

Modificar a lista de entrada durante o agrupamento. Adicionar ou remover elementos da lista durante um loop de agrupamento produz resultados imprevisíveis. Construa o dict agrupado primeiro e depois modifique os elementos.

defaultdict aparecendo no repr. defaultdict(list, {...}) parece diferente de um dict simples no repr. Envolva-o com dict(grouped) quando precisar de uma saída em dict simples.

Prática

Prática
What must you do before passing a list to itertools.groupby() to get one group per unique key?
What must you do before passing a list to itertools.groupby() to get one group per unique key?
Was this page helpful?