collections apporte justement des structures spécialisées qui évitent des boucles répétitives et des bricolages fragiles. Ici, je vais montrer à quoi servent ces types, quand les utiliser et quand rester sur les conteneurs natifs.
Les conteneurs spécialisés simplifient les cas pratiques sans alourdir le code
- Counter sert à compter des occurrences et à repérer rapidement les valeurs les plus fréquentes.
- defaultdict réduit le code répétitif quand il faut regrouper ou accumuler des éléments.
- deque est le bon choix pour une file d’attente, une pile double ou une fenêtre glissante.
- namedtuple, OrderedDict et ChainMap couvrent des besoins plus précis: objets immuables, ordre manipulé et configuration en couches.
- Pour un simple ordre d’insertion, le
dictnatif suffit souvent dans les versions récentes de Python.
Ce que le module apporte vraiment
Je vois souvent collections utilisé trop tôt, comme si c’était une boîte à outils magique. En réalité, son intérêt est plus simple: chaque structure y encode une intention claire. Un Counter dit que vous comptez, un deque dit que vous poussez aux extrémités, un defaultdict dit que certaines clés doivent naître avec une valeur par défaut.
Le détail qui compte, c’est que tout cela fait partie de la bibliothèque standard. Il n’y a rien à installer, et la lecture du code reste alignée avec les habitudes du langage. Cette proximité rend le module utile dans des projets backend, des scripts d’analyse ou des services web où l’on veut aller vite sans sacrifier la clarté.
Cette valeur n’apparaît vraiment que si le type choisi reflète le problème réel. Le reste de l’article suit donc une logique pragmatique: je pars du besoin concret, puis je choisis la structure qui le traduit le mieux.

Choisir la bonne structure selon le besoin
Quand je dois décider vite, je compare toujours l’opération dominante: compter, empiler, regrouper, nommer, ordonner ou superposer des sources. C’est plus fiable que de mémoriser une liste de classes par cœur.
| Structure | Quand l’utiliser | Point fort | Limite à connaître |
|---|---|---|---|
Counter |
Fréquences, top-N, multiset simple | Compteur prêt à l’emploi, zéro implicite pour les clés absentes | Peut garder des valeurs nulles ou négatives si vous ne nettoyez pas |
defaultdict |
Regroupement, accumulation, index inversé | Évite setdefault ou les tests de clé à répétition |
Crée des clés automatiquement, ce qui peut masquer une faute de frappe |
deque |
File FIFO, pile double, fenêtre glissante | Insertions et suppressions rapides aux deux extrémités | Moins adapté aux accès aléatoires au milieu |
namedtuple |
Petits enregistrements immuables | Accès par nom sans perdre la légèreté du tuple | Si l’objet grossit, une dataclass devient souvent plus lisible |
OrderedDict |
Ordre manipulé explicitement, cache LRU, déplacement d’éléments |
move_to_end() et popitem(last=False)
|
Pour un simple ordre d’insertion, un dict moderne suffit souvent |
ChainMap |
Préférences, variables d’environnement, configuration par couches | Lecture hiérarchique sans fusionner des dictionnaires | Les écritures ciblent le premier mapping |
La règle la plus utile, ici, est presque toujours la même: ne choisissez pas un conteneur parce qu’il “a l’air propre”, choisissez-le parce qu’il correspond au geste principal que votre code va répéter.
Cette grille devient vraiment parlante dès qu’on l’applique aux cas les plus fréquents, à commencer par le comptage et le regroupement.
Compter et regrouper sans écrire de code répétitif
Pour des journaux applicatifs, des tags, des catégories produit ou des événements backend, je commence souvent par Counter ou defaultdict. Les deux évitent beaucoup de code défensif, mais ils n’ont pas le même rôle.
Counter quand il faut mesurer des fréquences
Counter est pensé pour les cas où l’on veut savoir “combien de fois”. Il accepte un itérable, un mapping ou des mots-clés, et il renvoie 0 pour une clé absente au lieu de lever une erreur. C’est pratique pour compter des occurrences de termes dans des journaux, des statuts HTTP ou des valeurs issues d’un export.
from collections import Counter
statuses = ["200", "200", "404", "500", "200", "404"]
counts = Counter(statuses)
print(counts["200"]) # 3
print(counts.most_common(2)) # [('200', 3), ('404', 2)]
Si je veux le total brut, total() m’évite d’écrire sum(counts.values()). Je garde aussi en tête qu’un Counter peut conserver des comptes nuls ou négatifs; si la clé ne doit plus exister, je la supprime vraiment avec del.
defaultdict quand il faut remplir des groupes
defaultdict me sert dès que je veux accumuler des listes, des ensembles ou des nombres sans écrire une condition à chaque ajout. C’est plus court qu’un dict.setdefault() répété partout, et plus lisible dans un pipeline de traitement.
from collections import defaultdict
users_by_role = defaultdict(list)
for role, user in [("admin", "Lea"), ("dev", "Nora"), ("admin", "Marc")]:
users_by_role[role].append(user)
print(users_by_role["admin"]) # ['Lea', 'Marc']
Le point de vigilance est simple: parce que la clé se crée automatiquement, une erreur de nom de clé peut passer inaperçue plus longtemps. Je recommande defaultdict quand la création implicite est vraiment un avantage métier, pas juste un raccourci syntaxique.
Une fois le comptage et le regroupement en place, le prochain besoin est souvent la gestion d’une file d’attente ou d’une fenêtre glissante, et c’est là que deque devient intéressant.
Utiliser deque quand les extrémités comptent
La grande force de deque, c’est la rapidité aux deux bouts. Si votre code pousse et retire souvent des éléments au début ou à la fin, une liste classique devient moins adaptée, surtout quand le volume grandit. Pour une file FIFO, une pile double, un buffer ou un parcours par niveaux, deque est généralement le bon outil.
from collections import deque
file_attente = deque()
file_attente.append("commande-1")
file_attente.append("commande-2")
premiere = file_attente.popleft()
Je trouve aussi maxlen très utile pour les fenêtres glissantes: quand la taille maximale est atteinte, les anciens éléments sortent automatiquement. C’est propre pour suivre les derniers événements d’un service, conserver un historique court ou calculer une moyenne mobile simple.
from collections import deque
historique = deque(maxlen=3)
for valeur in [10, 20, 30, 40]:
historique.append(valeur)
print(list(historique)) # [20, 30, 40]
rotate() devient vite pratique pour un tampon circulaire ou une petite rotation d’ordre sans reconstruire la structure. Deux limites méritent d’être dites franchement. D’abord, deque n’est pas conçu pour les accès fréquents au milieu. Ensuite, extendleft() ajoute en inversant l’ordre d’entrée, ce qui surprend encore beaucoup de débutants. Si votre usage ressemble plus à un tableau qu’à une file, une liste reste souvent plus simple.
Quand les données ne sont plus seulement à déplacer mais à structurer, on entre dans un autre terrain: celui des petits objets, des dictionnaires ordonnés et des vues de configuration.
Structurer des données et superposer des contextes
namedtuple pour des enregistrements immuables et lisibles
namedtuple garde la légèreté d’un tuple, mais ajoute des noms de champs. C’est utile pour des coordonnées, des résultats de requête, des lignes de données ou des retours de fonction très compacts. Là où un tuple brut force à se souvenir de l’ordre des valeurs, namedtuple documente l’intention directement dans le code.
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 8)
print(p.x) # 3
print(p.y) # 8
Je le garde surtout pour les objets simples et immuables. Dès qu’il faut ajouter de la logique métier, des valeurs modifiables ou des annotations de type plus explicites, une dataclass devient souvent un meilleur choix.
OrderedDict pour les cas où l’ordre se manipule vraiment
Dans le Python moderne, un dict ordinaire conserve déjà l’ordre d’insertion. C’est pourquoi je vois OrderedDict comme un outil spécialisé, pas comme un remplacement automatique. Son intérêt reste réel quand on doit déplacer des éléments avec move_to_end(), retirer le premier élément avec popitem(last=False) ou comparer deux dictionnaires en tenant compte de l’ordre.
Autrement dit, si vous voulez seulement que l’ordre d’insertion soit stable, le type natif suffit. Si vous voulez agir sur l’ordre, OrderedDict reprend l’avantage.
Lire aussi : Python filter() - Quand l'utiliser et ses meilleures alternatives
ChainMap pour des configurations en couches
ChainMap est très pratique pour assembler plusieurs sources sans tout fusionner. Je m’en sers mentalement comme d’une hiérarchie de lecture: arguments de ligne de commande, variables d’environnement, puis valeurs par défaut. C’est exactement le genre de mécanique qu’on retrouve dans beaucoup d’outils backend et d’applications web.
from collections import ChainMap
defaults = {"theme": "light", "debug": False}
env = {"debug": True}
cli = {"theme": "dark"}
config = ChainMap(cli, env, defaults)
print(config["theme"]) # dark
print(config["debug"]) # True
Le piège à connaître, ici, est simple: une écriture modifie le premier dictionnaire seulement. Si vous voulez propager un changement plus loin dans la chaîne, il faut le faire explicitement. Cette contrainte est utile quand on la comprend, gênante quand on l’ignore.
Avec ces trois types, on passe d’un simple stockage à une vraie modélisation du comportement, et c’est souvent là que la qualité du code fait un saut visible.
Quand rester sur les types natifs plutôt que d’ajouter une structure spéciale
Je ne recommande pas de remplacer dict, list ou set par un conteneur de collections par réflexe. Dans beaucoup de scripts et de services, le type natif est plus clair, plus familier et tout aussi performant pour le besoin réel. Le bon test est simple: si le type spécial n’ajoute ni intention lisible ni gain concret, il alourdit plutôt qu’il n’aide.
- Gardez
dictsi vous avez seulement besoin d’un mapping classique avec ordre d’insertion stable. - Gardez
listsi vous ajoutez et lisez surtout à la fin. - Gardez
setsi vous cherchez l’unicité et des tests d’appartenance rapides. - Prenez
Counteroudefaultdictseulement quand ils suppriment vraiment du code répétitif. - Prenez
dequeseulement si les opérations aux extrémités dominent.
En pratique, le meilleur code est souvent celui qui utilise le conteneur le plus banal possible, sauf quand une structure spécialisée raconte mieux le problème. C’est une discipline de lisibilité autant que de technique.
Si je devais résumer mon réflexe avant d’ajouter une abstraction de plus, je me poserais une dernière série de questions très concrètes.
Le réflexe que j’applique avant d’ajouter un conteneur spécial
Avant de trancher, je vérifie quatre choses: est-ce que l’opération dominante est le comptage, la file, le regroupement, l’ordre ou la superposition de sources ? est-ce qu’un type natif fait déjà le travail sans ambiguïté ? est-ce que la structure choisie rend le code plus facile à relire dans six mois ? et est-ce qu’elle évite réellement du code de garde inutile ?Si la réponse est oui à l’une de ces questions et qu’elle reste simple à expliquer, le choix est probablement bon. Sinon, je reviens au type standard, parce que la simplicité gagne presque toujours sur le long terme dans un code backend qui doit vivre.
Le vrai intérêt du module collections n’est pas de multiplier les options, mais de faire apparaître plus clairement l’intention derrière chaque bloc de code, sans sacrifier la précision ni la vitesse quand elles comptent vraiment.