Python defaultdict : Simplifiez les opérations sur les dictionnaires avec des valeurs par défaut
Updated on
Tout développeur Python a rencontré ce problème : vous écrivez une boucle propre pour regrouper ou compter des éléments à l'aide d'un dictionnaire, vous exécutez le code, et un KeyError fait planter tout le script parce qu'une clé n'existait pas encore. La solution habituelle est de parsemer des vérifications if key in dict ou des blocs try/except KeyError partout. Votre logique pour regrouper dix lignes de données gonfle soudainement à vingt lignes de code défensif répétitif.
Cela empire à grande échelle. Lorsque vous construisez des listes d'adjacence pour des graphes, agrégez des données de log ou comptez les fréquences de mots sur des millions d'enregistrements, ces clauses de protection s'accumulent. Elles vous ralentissent en tant que développeur, rendent le code plus difficile à relire et introduisent des bugs subtils lorsque vous oubliez une vérification dans une branche.
Le collections.defaultdict de Python élimine toute cette catégorie de problèmes. C'est une sous-classe de dictionnaire qui appelle une fonction fabrique pour fournir automatiquement les valeurs manquantes. Plus de KeyError, plus de clauses de protection, plus de code répétitif.
Qu'est-ce que defaultdict ?
Le defaultdict est une sous-classe du dict intégré de Python. La différence clé : lorsque vous accédez à une clé qui n'existe pas, defaultdict la crée automatiquement avec une valeur par défaut au lieu de lever un KeyError.
from collections import defaultdict
# Un dict classique lève KeyError
regular = {}
# regular['missing'] # KeyError: 'missing'
# defaultdict crée la valeur automatiquement
dd = defaultdict(int)
dd['missing'] # Retourne 0, et 'missing' est maintenant une clé
print(dd) # defaultdict(<class 'int'>, {'missing': 0})Le constructeur prend une fonction fabrique comme premier argument. Fabriques courantes :
int-- retourne0list-- retourne[]set-- retourneset()str-- retourne""lambda: value-- retourne toute valeur par défaut personnalisée
defaultdict(int) -- Le modèle de comptage
L'utilisation la plus courante. Chaque nouvelle clé commence à 0, vous pouvez donc incrémenter immédiatement.
from collections import defaultdict
words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
# Sans defaultdict
counts_regular = {}
for word in words:
if word in counts_regular:
counts_regular[word] += 1
else:
counts_regular[word] = 1
# Avec defaultdict(int) -- propre et direct
counts = defaultdict(int)
for word in words:
counts[word] += 1
print(dict(counts))
# {'apple': 3, 'banana': 2, 'cherry': 1}defaultdict(list) -- Le modèle de regroupement
Regroupez les éléments associés. Chaque nouvelle clé commence avec une liste vide.
from collections import defaultdict
students = [
('Math', 'Alice'),
('Science', 'Bob'),
('Math', 'Charlie'),
('Science', 'Diana'),
('Math', 'Eve'),
('History', 'Frank'),
]
groups = defaultdict(list)
for subject, student in students:
groups[subject].append(student)
for subject, names in groups.items():
print(f"{subject}: {', '.join(names)}")
# Math: Alice, Charlie, Eve
# Science: Bob, Diana
# History: FrankRegrouper des enregistrements par plusieurs champs
from collections import defaultdict
sales = [
{'region': 'East', 'product': 'Widget', 'amount': 100},
{'region': 'West', 'product': 'Gadget', 'amount': 200},
{'region': 'East', 'product': 'Widget', 'amount': 150},
{'region': 'West', 'product': 'Widget', 'amount': 300},
]
by_region_product = defaultdict(list)
for sale in sales:
key = (sale['region'], sale['product'])
by_region_product[key].append(sale['amount'])
for (region, product), amounts in by_region_product.items():
total = sum(amounts)
print(f"{region} - {product}: {amounts} (total: {total})")defaultdict(set) -- Regroupement unique
Collectez automatiquement les valeurs uniques par clé.
from collections import defaultdict
edges = [
('Alice', 'Bob'), ('Alice', 'Charlie'),
('Bob', 'Alice'), ('Bob', 'Diana'),
('Alice', 'Bob'), # duplicate
]
connections = defaultdict(set)
for person, friend in edges:
connections[person].add(friend)
for person, friends in connections.items():
print(f"{person} is connected to: {friends}")
# Alice is connected to: {'Bob', 'Charlie'}
# Bob is connected to: {'Alice', 'Diana'}defaultdict(lambda: value) -- Valeurs par défaut personnalisées
Lorsque les types intégrés ne conviennent pas, utilisez un lambda pour retourner n'importe quelle valeur par défaut.
from collections import defaultdict
# Valeur par défaut 'N/A' pour les entrées manquantes
status = defaultdict(lambda: 'N/A')
status['server1'] = 'running'
status['server2'] = 'stopped'
print(status['server3']) # N/A
# Solde de départ par défaut
accounts = defaultdict(lambda: 100.0)
accounts['alice'] += 50
accounts['bob'] -= 30
print(dict(accounts)) # {'alice': 150.0, 'bob': 70.0}Dictionnaire par défaut avec des valeurs structurées
from collections import defaultdict
def default_profile():
return {'score': 0, 'level': 1, 'items': []}
profiles = defaultdict(default_profile)
profiles['player1']['score'] += 100
profiles['player1']['items'].append('sword')
profiles['player2']['level'] = 5
print(profiles['player1'])
# {'score': 100, 'level': 1, 'items': ['sword']}
print(profiles['player3'])
# {'score': 0, 'level': 1, 'items': []}defaultdict imbriqué -- Structures arborescentes
L'un des modèles les plus puissants est l'utilisation récursive de defaultdict pour créer des dictionnaires auto-vivifiants.
from collections import defaultdict
def tree():
return defaultdict(tree)
taxonomy = tree()
taxonomy['Animal']['Mammal']['Dog'] = 'Canis lupus familiaris'
taxonomy['Animal']['Mammal']['Cat'] = 'Felis catus'
taxonomy['Animal']['Bird']['Eagle'] = 'Aquila chrysaetos'
taxonomy['Plant']['Tree']['Oak'] = 'Quercus'
print(taxonomy['Animal']['Mammal']['Dog']) # Canis lupus familiarisAgrégation multi-niveaux
from collections import defaultdict
sales_data = [
(2025, 'Q1', 'Widget', 500),
(2025, 'Q1', 'Gadget', 300),
(2025, 'Q2', 'Widget', 700),
(2026, 'Q1', 'Widget', 600),
]
report = defaultdict(lambda: defaultdict(lambda: defaultdict(int)))
for year, quarter, product, amount in sales_data:
report[year][quarter][product] += amount
print(report[2025]['Q1']['Widget']) # 500
print(report[2026]['Q1']['Widget']) # 600defaultdict vs dict.setdefault() vs get() -- Comparaison
| Caractéristique | defaultdict | dict.setdefault() | dict.get() |
|---|---|---|---|
| Import requis | Oui (collections) | Non | Non |
| Crée la clé automatiquement | Oui | Oui | Non |
| Modifie le dict à l'accès | Oui | Oui | Non |
| Défaut personnalisé par appel | Non (fabrique globale) | Oui | Oui |
| Performance (répétée) | Plus rapide | Plus lent (surcoût d'appel de méthode) | Plus rapide (pas de mutation) |
| Idéal pour | Accumulation répétée | Défauts ponctuels | Lecture avec repli |
Quand utiliser chacun :
defaultdict: construire des valeurs sur de nombreuses itérations (comptage, regroupement)dict.setdefault(): avoir occasionnellement besoin d'une valeur par défaut pour une clé spécifiquedict.get(): lire une valeur avec un repli sans modifier le dictionnaire
Convertir defaultdict en dict classique
from collections import defaultdict
import json
def defaultdict_to_dict(d):
"""Recursively convert defaultdict to regular dict."""
if isinstance(d, defaultdict):
d = {k: defaultdict_to_dict(v) for k, v in d.items()}
return d
nested = defaultdict(lambda: defaultdict(int))
nested['x']['y'] = 10
nested['a']['b'] = 20
regular = defaultdict_to_dict(nested)
print(json.dumps(regular)) # {"x": {"y": 10}, "a": {"b": 20}}Vous pouvez également désactiver la fabrique par défaut en la définissant sur None :
dd = defaultdict(int)
dd['a'] += 1
dd.default_factory = None
# dd['missing'] # Lève maintenant KeyErrorExemples pratiques
Liste d'adjacence pour les graphes
from collections import defaultdict, deque
edges = [('A', 'B'), ('A', 'C'), ('B', 'D'), ('C', 'D'), ('D', 'E')]
graph = defaultdict(list)
for src, dst in edges:
graph[src].append(dst)
graph[dst].append(src) # undirected graph
def bfs(graph, start):
visited = set()
queue = deque([start])
order = []
while queue:
node = queue.popleft()
if node not in visited:
visited.add(node)
order.append(node)
queue.extend(graph[node])
return order
print(bfs(graph, 'A')) # ['A', 'B', 'C', 'D', 'E']Index inversé pour la recherche de texte
from collections import defaultdict
documents = {
'doc1': 'python is a great programming language',
'doc2': 'data science uses python extensively',
'doc3': 'machine learning with python and data',
}
index = defaultdict(set)
for doc_id, text in documents.items():
for word in text.split():
index[word.lower()].add(doc_id)
def search(query):
return index.get(query.lower(), set())
print(search('python')) # {'doc1', 'doc2', 'doc3'}
print(search('data')) # {'doc2', 'doc3'}Visualiser les données groupées avec PyGWalker
Après avoir regroupé et agrégé des données avec defaultdict, vous souhaitez souvent visualiser les résultats. PyGWalker (opens in a new tab) transforme votre DataFrame pandas en une interface de visualisation interactive directement dans Jupyter :
from collections import defaultdict
import pandas as pd
import pygwalker as pyg
sales = [
('Electronics', 'Laptop', 1200),
('Electronics', 'Phone', 800),
('Clothing', 'Shirt', 45),
('Clothing', 'Jacket', 120),
]
totals = defaultdict(lambda: defaultdict(int))
for category, product, amount in sales:
totals[category][product] += amount
rows = []
for category, products in totals.items():
for product, total in products.items():
rows.append({'category': category, 'product': product, 'total': total})
df = pd.DataFrame(rows)
walker = pyg.walk(df)FAQ
Qu'est-ce que defaultdict en Python ?
defaultdict est une sous-classe de dictionnaire dans collections qui fournit une valeur par défaut pour les clés manquantes. Au lieu de lever un KeyError, il appelle une fonction fabrique (comme int, list ou set) pour créer et stocker automatiquement une valeur par défaut.
Quelle est la différence entre dict et defaultdict ?
La seule différence fonctionnelle est la façon dont ils gèrent les clés manquantes. Un dict classique lève un KeyError. Un defaultdict appelle sa fonction default_factory pour créer une valeur par défaut. À tous les autres égards, ils se comportent de manière identique.
Quand dois-je utiliser defaultdict(list) vs defaultdict(set) ?
Utilisez defaultdict(list) lorsque vous souhaitez regrouper des éléments et préserver les doublons et l'ordre d'insertion. Utilisez defaultdict(set) lorsque vous souhaitez collecter uniquement des éléments uniques par clé.
Puis-je sérialiser un defaultdict en JSON ?
Oui, mais pour les objets defaultdict imbriqués, convertissez-les d'abord en dict classique à l'aide d'une fonction de conversion récursive. Vous pouvez également définir default_factory = None pour empêcher la création accidentelle de clés avant la sérialisation.
Comment créer un defaultdict imbriqué ?
Définissez une fonction fabrique récursive : def tree(): return defaultdict(tree). Pour une imbrication à deux niveaux plus simple, utilisez defaultdict(lambda: defaultdict(int)).
Conclusion
Le collections.defaultdict de Python est l'un des outils les plus pratiques de la bibliothèque standard. Il transforme des modèles d'accumulation de dictionnaires verbeux et sujets aux erreurs en lignes uniques propres. Utilisez defaultdict(int) pour le comptage, defaultdict(list) pour le regroupement, defaultdict(set) pour la collection unique et defaultdict imbriqué pour les données hiérarchiques.
Le point essentiel : si vous vous retrouvez à écrire if key not in dict avant chaque opération sur un dictionnaire, remplacez ce dictionnaire par un defaultdict. Votre code sera plus court, plus rapide et bien plus facile à maintenir.