Module collections de Python
Apprenez le module collections de Python : Counter, defaultdict, namedtuple, deque, OrderedDict et ChainMap — avec des exemples pratiques et quand les utiliser.
Le module intégré collections de Python fournit des types de conteneurs spécialisés qui étendent ou remplacent la liste, le dict et le tuple standard. Chaque type résout un problème spécifique qui nécessiterait sinon plusieurs lignes supplémentaires de gestion manuelle.
Ce chapitre couvre les six types les plus couramment utilisés : Counter, defaultdict, namedtuple, deque, OrderedDict et ChainMap. Pour chacun, vous verrez quel problème il résout, comment le créer et l'utiliser, ainsi que les pièges à éviter.
Aucune installation n'est nécessaire — collections est livré avec chaque installation de Python 3 :
from collections import Counter, defaultdict, namedtuple, deque, OrderedDict, ChainMapCounter
Counter est une sous-classe de dict conçue pour compter des objets hachables. Vous lui passez un itérable (ou une chaîne, ou des arguments nommés) et il renvoie un objet de type dictionnaire où les clés sont les éléments et les valeurs sont leurs occurrences.
Créer un Counter
from collections import Counter
# From a list
word_list = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
c = Counter(word_list)
print(c)
# Output: Counter({'apple': 3, 'banana': 2, 'cherry': 1})Les clés manquantes renvoient 0 au lieu de lever une KeyError :
print(c['apple']) # 3
print(c['mango']) # 0 — no KeyErrorÉléments les plus fréquents
most_common(n) renvoie les n éléments avec le plus grand nombre d'occurrences sous forme de liste de tuples (element, count), triés du plus au moins fréquent :
print(c.most_common(2))
# Output: [('apple', 3), ('banana', 2)]Omettez n pour obtenir tous les éléments triés par fréquence.
Arithmétique des Counter
Les Counter prennent en charge l'addition, la soustraction, l'intersection et l'union :
a = Counter(['a', 'a', 'b']) # Counter({'a': 2, 'b': 1})
b = Counter(['a', 'b', 'b', 'c']) # Counter({'b': 2, 'a': 1, 'c': 1})
print(a + b) # Counter({'a': 3, 'b': 3, 'c': 1})
print(a - b) # Counter({'a': 1}) — only positive counts kept
print(a & b) # Counter({'a': 1, 'b': 1}) — minimum of each count
print(a | b) # Counter({'a': 2, 'b': 2, 'c': 1}) — maximum of each countQuand utiliser Counter : décompte de votes, fréquences de mots, comptage de caractères, construction d'histogrammes.
Piège du Counter : la soustraction ne conserve que les positifs
a - b supprime silencieusement les éléments dont le résultat serait nul ou négatif. Si vous avez besoin de conserver des comptes négatifs, utilisez subtract() à la place :
a = Counter({'x': 2})
b = Counter({'x': 5})
a.subtract(b)
print(a) # Counter({'x': -3}) — negative count preserveddefaultdict
defaultdict est une sous-classe de dict qui appelle une fonction de fabrique pour fournir une valeur par défaut chaque fois que vous accédez à une clé qui n'existe pas encore. Cela élimine le besoin de clauses de garde if key not in d:.
Créer un defaultdict
Passez la fabrique comme premier argument :
from collections import defaultdict
dd = defaultdict(int) # default value: int() == 0
words = ['cat', 'dog', 'cat', 'bird', 'dog', 'cat']
for word in words:
dd[word] += 1 # no KeyError on first access
print(dict(dd))
# Output: {'cat': 3, 'dog': 2, 'bird': 1}Sans defaultdict, vous auriez besoin de dd[word] = dd.get(word, 0) + 1 ou d'un Counter.
Regrouper des éléments avec list comme fabrique
groups = defaultdict(list)
data = [('fruit', 'apple'), ('veggie', 'carrot'), ('fruit', 'banana'), ('veggie', 'broccoli')]
for category, item in data:
groups[category].append(item)
print(dict(groups))
# Output: {'fruit': ['apple', 'banana'], 'veggie': ['carrot', 'broccoli']}Fonctions de fabrique courantes
| Fabrique | Valeur par défaut | Utilisation typique |
|---|---|---|
int | 0 | Comptage |
float | 0.0 | Accumulation de sommes |
list | [] | Regroupement d'éléments |
set | set() | Collecte de valeurs uniques |
str | '' | Construction de chaînes |
dict | {} | Mappages imbriqués |
Vous pouvez également passer une lambda sans argument pour une valeur par défaut personnalisée : defaultdict(lambda: 'N/A').
Piège du defaultdict : accéder à une clé la crée
Contrairement à dict.get(), un simple accès dd[key] sur une clé manquante insère cette clé avec la valeur par défaut. Cela peut vous surprendre lors de l'itération ou de la vérification de l'appartenance :
dd = defaultdict(int)
print('foo' in dd) # False — key does not exist yet
_ = dd['foo'] # access inserts the key
print('foo' in dd) # True — key was silently createdUtilisez dd.get('foo') ou 'foo' in dd quand vous voulez vérifier sans effets de bord.
Quand utiliser defaultdict : regroupement de données, construction de listes d'adjacence pour des graphes, tout modèle où vous initialisez puis mettez à jour.
namedtuple
namedtuple crée une nouvelle classe dont les instances sont comme des tuples ordinaires mais avec des champs nommés. Le résultat est immuable, économe en mémoire (pas de __dict__ par instance) et auto-documenté.
Créer un namedtuple
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 7)
print(p) # Point(x=3, y=7)
print(p.x) # 3
print(p.y) # 7
print(p[0]) # 3 — index access still worksLe premier argument de namedtuple() est le nom du type (utilisé dans repr). Le deuxième argument est une liste de noms de champs (ou une chaîne séparée par des espaces/virgules : 'x y').
Exemple pratique
Employee = namedtuple('Employee', ['name', 'department', 'salary'])
emp = Employee('Alice', 'Engineering', 95000)
print(emp.name, emp.department, emp.salary)
# Output: Alice Engineering 95000L'accès par nom (emp.name) est bien plus clair que l'accès positionnel (row[0]) lors de la lecture de données depuis des fichiers CSV ou des lignes de base de données.
Méthodes utiles du namedtuple
# Convert to an ordered dictionary
print(p._asdict()) # {'x': 3, 'y': 7}
# Create a modified copy (namedtuples are immutable)
p2 = p._replace(x=10)
print(p2) # Point(x=10, y=7)
print(p) # Point(x=3, y=7) — original unchangednamedtuple vs dataclass
Python 3.7 a introduit dataclasses.dataclass comme alternative. Choisissez namedtuple quand vous voulez de l'immuabilité et une pleine compatibilité tuple (décomposition, indexation, hachage). Choisissez dataclass quand vous avez besoin de champs mutables, de fabriques par défaut ou de méthodes.
Quand utiliser namedtuple : représenter des enregistrements (lignes de base de données, lignes CSV, paires de coordonnées, couleurs RGB) où l'immuabilité et la faible empreinte mémoire sont importantes.
deque
deque (file double extrémité, prononcé "deck") est une séquence optimisée pour des ajouts et suppressions en O(1) aux deux extrémités. Une liste ordinaire réalise un append en O(1) et un insert(0, …) en O(n) ; deque réalise O(1) aux deux extrémités.
Créer un deque
from collections import deque
d = deque([1, 2, 3])
print(d) # deque([1, 2, 3])Ajout et suppression d'éléments
d.append(4) # add to right
d.appendleft(0) # add to left
print(d) # deque([0, 1, 2, 3, 4])
d.pop() # remove from right → 4
d.popleft() # remove from left → 0
print(d) # deque([1, 2, 3])Rotation d'un deque
rotate(n) décale les éléments vers la droite de n positions (négatif = vers la gauche) :
d = deque([1, 2, 3])
d.rotate(1)
print(d) # deque([3, 1, 2])
d.rotate(-1)
print(d) # deque([1, 2, 3])deque borné (fenêtre glissante / tampon FIFO)
Définir maxlen limite la taille du deque. Quand de nouveaux éléments sont ajoutés au-delà de la limite, les éléments tombent automatiquement de l'extrémité opposée — idéal pour conserver les N derniers événements :
buffer = deque(maxlen=3)
for i in range(5):
buffer.append(i)
print(buffer) # deque([2, 3, 4], maxlen=3)Piège du deque : accès aléatoire lent en O(n)
deque ne prend pas en charge l'accès aléatoire efficace. d[500] est en O(n), pas en O(1) comme une liste. Si vous indexez fréquemment par position, utilisez une liste. Utilisez deque uniquement quand vous avez besoin d'ajouts et de suppressions rapides aux deux extrémités.
Quand utiliser deque : implémentation de files et de piles, algorithmes à fenêtre glissante, parcours en largeur, stockage des N dernières entrées de journal.
OrderedDict
Depuis Python 3.7, le dict ordinaire maintient l'ordre d'insertion. Alors pourquoi utiliser OrderedDict ?
Deux raisons restent pertinentes :
move_to_end()— permet de réordonner efficacement les clés vers le début ou la fin.- Égalité — deux instances d'
OrderedDictavec les mêmes clés mais dans des ordres d'insertion différents sont comparées comme inégales, contrairement aux dicts ordinaires.
Créer et réordonner un OrderedDict
from collections import OrderedDict
od = OrderedDict()
od['one'] = 1
od['two'] = 2
od['three'] = 3
print(list(od.keys())) # ['one', 'two', 'three']
od.move_to_end('one') # move 'one' to the end
print(list(od.keys())) # ['two', 'three', 'one']
od.move_to_end('three', last=False) # move 'three' to the front
print(list(od.keys())) # ['three', 'two', 'one']Égalité sensible à l'ordre
od1 = OrderedDict([('a', 1), ('b', 2)])
od2 = OrderedDict([('b', 2), ('a', 1)])
print(od1 == od2) # False — different order
d1 = {'a': 1, 'b': 2}
d2 = {'b': 2, 'a': 1}
print(d1 == d2) # True — regular dicts ignore orderQuand utiliser OrderedDict : implémentations de cache LRU (déplacer la clé récemment utilisée à la fin), tout algorithme où l'ordre d'insertion doit faire partie de l'égalité.
ChainMap
ChainMap regroupe plusieurs dictionnaires en une seule vue logique. Les recherches parcourent les mappages dans l'ordre ; les écritures et les suppressions n'affectent toujours que le premier mappage.
Utilisation de base
from collections import ChainMap
defaults = {'color': 'blue', 'size': 'medium', 'theme': 'light'}
overrides = {'color': 'red', 'size': 'large'}
combined = ChainMap(overrides, defaults)
print(combined['color']) # 'red' — found in overrides first
print(combined['theme']) # 'light' — not in overrides, falls back to defaultsLes écritures vont uniquement dans le premier mappage :
combined['font'] = 'serif'
print(overrides) # {'color': 'red', 'size': 'large', 'font': 'serif'}
print(defaults) # {'color': 'blue', 'size': 'medium', 'theme': 'light'} — unchangedSimuler des portées de variables avec new_child()
base = ChainMap({'x': 1})
child = base.new_child({'x': 99, 'y': 2})
print(child['x']) # 99 — child scope shadows parent
print(child['y']) # 2
print(child.parents['x']) # 1 — access parent scope directlynew_child() renvoie un nouveau ChainMap avec un dict vide fraîchement ajouté en tête, ce qui modélise les propres règles de portée de Python (local → englobant → global → intégré) en interne.
Quand utiliser ChainMap : superposition de configurations (remplacements utilisateur → valeurs par défaut du projet → valeurs par défaut globales), implémentation d'environnements à portée, combinaison d'arguments CLI avec des variables d'environnement et des fichiers de configuration.
Choisir le bon type
| Vous avez besoin de… | Utilisez |
|---|---|
| Compter les occurrences d'éléments | Counter |
Éviter KeyError avec une valeur par défaut | defaultdict |
| Représenter un enregistrement avec des champs nommés | namedtuple |
| Ajouts/suppressions rapides aux deux extrémités, ou un tampon borné | deque |
Égalité de dict sensible à l'ordre, ou move_to_end() | OrderedDict |
| Fusionner plusieurs dicts en une seule vue sans copie | ChainMap |
Pour en savoir plus sur les types de base que ceux-ci étendent, consultez Python Dictionaries, Python Lists et Python Tuples. Pour les aides basées sur les itérateurs dans la bibliothèque standard, consultez le module Python itertools.