defaultdict : L'évolution du dictionnaire sans conditions

Il existe de nombreuses bibliothèques externes en Python, mais en comprenant bien la bibliothèque standard, vous pouvez écrire un code suffisamment puissant pour un usage pratique. Dans cet article, nous allons explorer en profondeur collections.defaultdict.

Avec cet article, vous comprendrez clairement quand, pourquoi et comment utiliser defaultdict au-delà d'une simple introduction conceptuelle.

Pour ceux qui se demandent sur la première classe de la série collections, Counter, je vous recommande de lire l'article précédent. Bibliothèque standard Python ① - collections.Counter


1. Concept de base : Qu'est-ce que defaultdict ?

Tux avec defaultdicts

defaultdict est une sous-classe de dictionnaires (dict) qui sont inclus dans le module collections de la bibliothèque standard de Python. Lorsque vous accédez à une clé qui n'existe pas dans un dictionnaire classique, une KeyError se produit, mais avec defaultdict, vous pouvez spécifier une fonction (fonction de fabrique) qui crée automatiquement une valeur par défaut, rendant votre code beaucoup plus propre et évitant les erreurs.


2. Utilisation de base

from collections import defaultdict

d = defaultdict(int)
d['apple'] += 1
print(d)  # defaultdict(<class 'int'>, {'apple': 1})

Ici, int() retourne 0 comme valeur par défaut. Lorsqu'on accède à la clé inexistant 'apple', il crée automatiquement 0 sans KeyError puis exécute +1.


3. Exemples de diverses valeurs par défaut

from collections import defaultdict

# Valeur par défaut : 0 (int)
counter = defaultdict(int)
counter['a'] += 1
print(counter)  # defaultdict(<class 'int'>, {'a': 1})

# Valeur par défaut : liste vide
group = defaultdict(list)
group['fruit'].append('apple')
group['fruit'].append('banana')
print(group)  # defaultdict(<class 'list'>, {'fruit': ['apple', 'banana']})

# Valeur par défaut : ensemble vide
unique_tags = defaultdict(set)
unique_tags['tags'].add('python')
unique_tags['tags'].add('coding')
print(unique_tags)  # defaultdict(<class 'set'>, {'tags': {'python', 'coding'}})

# Valeur par défaut : valeur initiale personnalisée
fixed = defaultdict(lambda: 100)
print(fixed['unknown'])  # 100

4. Exemples pratiques

1. Compter la fréquence des mots

words = ['apple', 'banana', 'apple', 'orange', 'banana']
counter = defaultdict(int)

for word in words:
    counter[word] += 1

print(counter)
# defaultdict(<class 'int'>, {'apple': 2, 'banana': 2, 'orange': 1})

👉 Counter vs defaultdict
Pour compter la fréquence des mots, collections.Counter() est plus spécialisé, et il est préférable de l'utiliser dans des cas nécessitant des statistiques ou une analyse de classement. Cependant, pour un simple comptage cumulatif, defaultdict(int) peut également être utilisé de manière concise.

2. Organiser des logs par groupe

logs = [
    ('2024-01-01', 'INFO'),
    ('2024-01-01', 'ERROR'),
    ('2024-01-02', 'DEBUG'),
]

grouped = defaultdict(list)
for date, level in logs:
    grouped[date].append(level)

print(grouped)
# defaultdict(<class 'list'>, {'2024-01-01': ['INFO', 'ERROR'], '2024-01-02': ['DEBUG']})

3. Organisation des tags uniques

entries = [
    ('post1', 'python'),
    ('post1', 'coding'),
    ('post1', 'python'),
]

tags = defaultdict(set)
for post, tag in entries:
    tags[post].add(tag)

print(tags)
# defaultdict(<class 'set'>, {'post1': {'python', 'coding'}})

5. Points à surveiller

  • defaultdict stocke un générateur de valeur par défaut en interne, donc lors de l'appel de repr(), il peut apparaître différemment d'un dict classique.
  • Cela peut poser des problèmes lors de la sérialisation JSON. Il est plus sûr de le traiter après l'avoir transformé en dict(d).
  • La valeur par défaut n'est créée que lors d'un accès via []. Elle ne sera pas créée si vous utilisez get().
from collections import defaultdict

d = defaultdict(list)
print(d.get('missing'))  # None
print(d['missing'])      # []

6. Quand est-il bon à utiliser ? – 3 avantages décisifs de defaultdict

defaultdict améliore simultanément la lisibilité, la maintenabilité et la sécurité dans des situations où vous utilisez fréquemment le modèle dict + condition. Vous aurez trois occasions de vous dire : 'Ah, dans ce cas, mieux vaut utiliser defaultdict !'

6-1. Code d'agrégation sans condition pour compter/cumul

from collections import defaultdict

# Dictionnaire classique
counts = {}
for item in items:
    if item not in counts:
        counts[item] = 0
    counts[item] += 1

# defaultdict
counts = defaultdict(int)
for item in items:
    counts[item] += 1

✔ La disparition des conditions rend le code plus concis et réduit les possibilités d'erreur.
✔ Ceci est particulièrement adapté à l'analyse de logs et au comptage de mots dans des ensembles de données volumineux.

6-2. Remplacement de setdefault lors de l'accumulation de listes/ensembles

from collections import defaultdict

# Dictionnaire classique
posts = {}
for tag, post in data:
    if tag not in posts:
        posts[tag] = []
    posts[tag].append(post)

# defaultdict
posts = defaultdict(list)
for tag, post in data:
    posts[tag].append(post)

✔ C'est beaucoup plus intuitif que setdefault() et reste propre même dans une boucle.
✔ C'est une structure optimisée pour le groupement de données.

6-3. Automatisation de l'initialisation lors de la création de dictionnaires imbriqués

# Dictionnaire classique
matrix = {}
if 'x' not in matrix:
    matrix['x'] = {}
matrix['x']['y'] = 10

# Imbrication avec defaultdict
matrix = defaultdict(lambda: defaultdict(int))  # Crée automatiquement defaultdict(int) à chaque fois qu'une clé est absente
matrix['x']['y'] += 10

✔ Vous pouvez facilement créer des structures de données imbriquées, ce qui est très avantageux pour les travaux sur des dictionnaires multidimensionnels.
✔ Cela est d'une grande utilité pour le data mining, le parsing et le stockage de structures en arbre.

lambda: defaultdict(int) est structurée pour retourner defaultdict(int) chaque fois qu'une clé est manquante, générant automatiquement un dictionnaire imbriqué.


7. Conclusion

collections.defaultdict peut sembler être une simple version extensible du dict pour les débutants, mais à l'usage, vous réaliserez que c'est un outil structurel qui rend le code plus clair et plus sûr.

  • Vous pouvez utiliser des dictionnaires sans craindre le KeyError.
  • Vous pouvez grouper et cumuler des données sans conditions.
  • Vous pouvez composer intuitivement des dictionnaires imbriqués.
# Exemples de gestion avec defaultdict pour plus de stabilité et de clarté
salaries = defaultdict(int)
for dept, amount in records:
    salaries[dept] += amount

Si vous pouvez prévenir les erreurs, améliorer la lisibilité et assurer la maintenabilité en une seule ligne de code,
defaultdict n'est pas seulement une fonction pratique, mais un outil central dans la manière de penser en Python.

Le prochain sujet sera pathlib.
Une façon moderne de traiter les fichiers et répertoires de manière orientée objet, utile pour débutants et intermédiaires. Les développeurs familiers avec os.path seront ravis de constater à quel point c'est plus simple ! Restez à l'écoute pour le prochain épisode.