W3docs

Regroupement de listes Python

Apprenez trois façons de regrouper des listes Python : defaultdict, itertools.groupby et les dict comprehensions — avec exemples et pièges.

Regrouper une liste signifie partitionner ses éléments en sous-collections partageant une clé commune — par exemple, regrouper des mots selon leur première lettre, ou regrouper des enregistrements selon un champ de catégorie. Python propose trois approches principales : une boucle manuelle avec collections.defaultdict, itertools.groupby de la bibliothèque standard, et les dict comprehensions. Ce chapitre explique chaque technique, quand choisir l'une plutôt qu'une autre, et les pièges à éviter.

Chapitres connexes : Python Lists · List Methods · List Comprehension · Loop Lists · collections Module

Ce que signifie « regrouper »

Étant donné une liste plate et une fonction de clé qui associe chaque élément à un libellé de groupe, l'objectif est de produire une correspondance entre chaque libellé et la liste des éléments qui lui appartiennent :

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

Les trois techniques ci-dessous produisent toutes ce type de résultat. Elles diffèrent par leur verbosité, leurs performances et les contraintes qu'elles imposent sur l'entrée.

Technique 1 : boucle manuelle avec defaultdict

collections.defaultdict est l'approche la plus courante et la plus flexible. Lorsque vous accédez à une clé qui n'existe pas encore, un defaultdict(list) crée automatiquement une liste vide pour cette clé, vous évitant ainsi d'avoir à écrire un test 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']

Pourquoi utiliser defaultdict plutôt qu'un dict ordinaire ?

Avec un dict ordinaire, vous devez effectuer une vérification explicite avant le premier 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)

Une alternative plus courte avec un dict ordinaire est dict.setdefault :

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

setdefault convient pour les scripts courts, mais defaultdict est plus rapide (pas de recherche répétée de clé) et exprime plus clairement l'intention.

Regroupement par une clé calculée

La clé peut être n'importe quelle expression, pas seulement un attribut. Ici, une liste d'entiers est divisée en groupes pairs et impairs :

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]

Regroupement d'une liste de dicts

C'est le scénario réel le plus courant — regrouper des lignes de données selon la valeur d'un champ :

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']

Technique 2 : itertools.groupby

itertools.groupby regroupe les éléments consécutifs partageant la même clé. Il est utile lorsque vous devez conserver la structure de longueur de séquence ou lorsque les données sont déjà triées et que vous souhaitez éviter de construire tout le dictionnaire en mémoire (il est paresseux/en flux).

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']

Le piège critique : trier avant groupby

groupby ne regroupe que les éléments consécutifs partageant la même clé. Si l'entrée n'est pas triée selon la clé, vous obtenez plusieurs petits groupes au lieu d'un seul groupe par clé :

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]

Triez toujours selon la même fonction de clé avant d'appeler 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]

Quand groupby est avantageux : traitement en flux de données volumineuses

Comme groupby retourne un itérateur, il ne charge pas tous les groupes en mémoire en même temps. Cela le rend utile pour traiter de grands fichiers triés ligne par ligne sans construire un dictionnaire complet.

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']

Technique 3 : Dict comprehension

Une dict comprehension construit le dictionnaire groupé en une seule expression. C'est concis, mais cela présente un inconvénient : la list comprehension interne réanalyse l'intégralité de l'entrée pour chaque clé unique, ce qui lui confère une complexité O(n × k) où k est le nombre de clés uniques. Pour les petites listes, c'est acceptable ; pour les grandes listes, préférez 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']

Cette technique est la plus lisible lorsque l'ensemble de clés est petit et déjà connu — par exemple, pour regrouper des résultats True/False ou un ensemble fixe de catégories.

Agréger les groupes après regroupement

Une étape courante après le regroupement est l'agrégation : calculer une somme, une moyenne, un minimum ou un comptage par groupe. Combinez defaultdict(list) avec l'arithmétique Python standard :

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

Choisir la bonne technique

SituationMeilleur choix
Regroupement général, ordre quelconquedefaultdict(list)
Besoin de traiter en flux de grandes données triéesitertools.groupby
Petite liste, expression concise en une ligneDict comprehension
L'entrée est déjà triéedefaultdict ou groupby
Besoin d'agréger (somme, moyenne, etc.)defaultdict(list) + arithmétique

Pièges courants

Oublier de trier avant groupby. groupby ne fusionne que les clés identiques consécutives. Triez toujours l'entrée avec sorted() selon la même fonction de clé avant de la passer à groupby.

Ne pas convertir list(group) immédiatement. L'itérateur de groupe retourné par groupby est épuisé dès que la boucle for externe passe à la clé suivante. Convertissez-le en liste dans le corps de la boucle si vous devez l'utiliser plus d'une fois.

Modifier la liste d'entrée pendant le regroupement. Ajouter ou supprimer des éléments de la liste pendant une boucle de regroupement produit des résultats imprévisibles. Construisez d'abord le dictionnaire groupé, puis modifiez les éléments.

defaultdict apparaissant dans repr. defaultdict(list, {...}) est différent d'un dict ordinaire dans repr. Encapsulez-le avec dict(grouped) lorsque vous avez besoin d'une sortie dict ordinaire.

Pratique

Pratique
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?