Le module collections en Python est un module qui fournit différents types de conteneurs visant à manipuler des types de données en Python, en particulier des itérables.
Un conteneur est un objet utilisé pour stocker différents objets et fournir un moyen d'accéder aux objets contenus et de les parcourir.
Ce module implémente des types de données de conteneurs spécialisés qui apportent des alternatives aux conteneurs natifs de Python de base, à savoir dict, list, set et tuple.
Liste des conteneurs
La liste des conteneurs du module collections est la suivante :
-
namedtuple : un conteneur pour créer des sous-classes de tuple avec des champs nommés
-
deque : un conteneur de type liste avec des ajouts rapides et des pops à chaque extrémité
-
ChainMap : un conteneur de type dict pour créer une vue unique de plusieurs mappages
-
Counter : un conteneur de type dict pour compter les objets hachables
-
OrderedDict : un conteneur de type dict qui se souvient que les entrées de commande ont été ajoutées
-
defaultdict : un conteneur de type dict qui appelle une fonction d’usine pour fournir les valeurs manquantes
-
UserDict : un wrapper autour des objets du dictionnaire pour faciliter le sous-classement des dicts
-
UserList : un wrapper autour des objets de la liste pour faciliter le sous-classement de la liste
-
UserString : un wrapper autour des objets de chaîne pour faciliter le sous-classement de chaîne
À noter
Dans ce guide, nous allons étudier seulement les 6 premiers, à savoir : namedtuple, deque, Counter, ChainMap, OrderedDict et defaultdict.
Le conteneur namedtuple
Rappel : Un tuple est un conteneur immuable de données souvent hétérogènes.
Le conteneur namedtuple permet d’ajouter des noms explicites à chaque élément d’un tuple pour rendre ces significations claires dans un programme Python.
Comme les dictionnaires, un tuple nommé (namedtuple) contient des clés qui sont hachées à une valeur particulière.
Mais au contraire de ces derniers, il prend en charge à la fois l’accès à partir de la clé mais aussi de l'indice, la fonctionnalité qui manque aux dictionnaires.
from collections import namedtuple Etudiant = namedtuple('Etudiant',['prenom','nom','age']) etu = Etudiant('Kévin','Silliau','20') # Accès avec l'index print(f"Le prénom de l'étudiant est {etu[0]}") # Accès avec le nom de la variable print(f"L'âge de l'étudiant est {etu.age}")
Voici une liste des méthodes intéressantes du conteneur namedtuple :
-
_asdict(): Permet de convertir une instance de namedtuple en dictionnaire, exemple :
Etudiant = namedtuple ('Etudiant',['prenom','age']) etu = Etudiant("Kévin",20) print(etu._asdict()) # affiche {'prenom': 'Kévin', 'age': 20} -
_replace(key=args): Retourner une nouvelle instance de notre tuple avec une valeur modifiée, exemple :
Etudiant = namedtuple ('Etudiant',['prenom','age']) etu = Etudiant("Kévin",20) print(etu._replace(prenom="David")) # affiche Etudiant(prenom='David', age=20) -
_fields: Récupérer les noms des champs de notre tuple. Elle est utile si on veut créer un nouveau tuple avec les champs d’un tuple existant, exemple :
Etudiant = namedtuple ('Etudiant',['prenom','age']) print(Etudiant._fields) #affiche: (‘prenom’,’age’) Passions = namedtuple ('Passions', ['sport', 'musique', 'jeu']) #Créer un nouveau tuple avec les champs de point et de color : Developpeur = namedtuple ('Developpeur', Etudiant._fields + Passions._fields) print(Developpeur("Kévin",20,"F1","KPOP","Minecraft")) #affiche: Developpeur(prenom='Kévin', age=20, sport='F1', musique='KPOP', jeu='Minecraft')
Le conteneur deque
Le conteneur deque (pour Doubly Ended Queue) est préféré à une liste (list) dans les cas où nous avons besoin d’opérations d’ajout et de suppression plus rapides à partir des deux extrémités du conteneur.
En effet, deque fournit une complexité temporelle O(1) pour les opérations d’ajout et de suppression par rapport à la liste qui fournit O(n) comme complexité temporelle.
À noter
La complexité égale à O(1) signifie que le temps nécessaire pour ajouter un élément au début de de liste n’augmentera pas, même si cette liste a des milliers ou des millions d’éléments.
Voici la liste des méthodes intéressantes du conteneur deque :
-
append(x) : ajoute une seule valeur du côté droit du deque
-
appendleft(x) : ajoute une seule valeur du côté gauche du deque
-
pop() : Pour faire sortir une seule valeur du côté droit du deque
-
popleft() : Pour faire sortir une seule valeur du côté gauche du deque
-
clear() : Pour vider le deque
Voici un exemple de leur utilisation :
from collections import deque
mon_deque = deque(["Kévin", "Thibault"])
mon_deque.appendleft("Gabriel") # ajout de "Gabriel" au début de mon_deque
mon_deque.append("Bob") # ajout de "Bob" à la fin de mon_deque
print(mon_deque) # affiche deque(['Gabriel', 'Kévin', 'Thibault', 'Bob'])
mon_deque.pop() # supprime Bob
mon_deque.popleft() # supprime Gabriel
print(mon_deque.clear()) # affiche None
Le conteneur ChainMap
Le conteneur ChainMap permet d'encapsuler de nombreux dictionnaires dans une seule unité.
Voici un exemple d'utilisation :
from collections import ChainMap
prof = {'prenom':"Kévin", 'nom':"Silliau"}
ecole = {'site':"docstring.fr", 'langage':"Python"}
ma_chainmap = ChainMap(prof, ecole)
print(ma_chainmap) # Affiche : ChainMap({'prenom': 'Kévin', 'nom': 'Silliau'}, {'site': 'docstring.fr', 'langage': 'Python'})
On peut aussi utiliser les ChainMap pour itérer et afficher les valeurs comme dans un simple dictionnaire, à la différence qu'ici un ChainMap peut en contenir plusieurs !
from collections import ChainMap
fruits = {'pommes': 1, 'bananes': 2}
legumes = {'haricots': 3, 'carottes': 4}
feculents = {'pâtes': 5, 'riz': 6}
ma_chainmap = ChainMap(fruits, legumes, feculents)
# Accéder à un élément spécifique à l'aide de sa clé
print(ma_chainmap['pommes']) # Affiche : 1
print(ma_chainmap.get('bananes')) # Affiche : 2
for key, value in ma_chainmap.items():
print(key, "->", value, end=" ; ")
# Affiche : pâtes -> 5 ; riz -> 6 ; haricots -> 3 ; carottes -> 4 ; pommes -> 1 ; bananes -> 2 ;
Le conteneur ChainMap possède une méthode et une propriété intéressant :
-
new_child(dict) : ajoute un nouveau dictionnaire à l'objet
ChainMap, on peut y passer directement un dictionnaire ou bien une variable contenant un dictionnaire. Ce dictionnaire sera le nouveau dictionnaire de début de liste. Si la méthode est appelée sans arguments, un dictionnaire vide sera ajouté en tête de liste. Exemple :
from collections import ChainMap fruits = {'pommes': 1, 'bananes': 2} legumes = {'haricots': 3, 'carottes': 4} feculents = {'pâtes': 5, 'riz': 6} ma_chainmap = ChainMap(fruits, legumes) ma_chainmap = ma_chainmap.new_child(feculents) # ou bien ma_chainmap = ma_chainmap.new_child({'pâtes': 5, 'riz': 6}) print(ma_chainmap) # Affiche : ChainMap({'pâtes': 5, 'riz': 6}, {'pommes': 1, 'bananes': 2}, {'haricots': 3, 'carottes': 4}, {'pâtes': 5, 'riz': 6}) -
parents : Renvoie un nouvel objet
ChainMapcontenant tous les dictionnaires de l'objet hormis le premier. Cette propriété est notamment utile pour ignorer le premier dictionnaire dans des recherches. Exemple :
from collections import ChainMap
fruits = {'pommes': 1, 'bananes': 2}
legumes = {'haricots': 3, 'carottes': 4}
feculents = {'pâtes': 5, 'riz': 6}
ma_chainmap = ChainMap(fruits, legumes, feculents)
print(ma_chainmap.parents) # Affiche : ChainMap({'haricots': 3, 'carottes': 4}, {'pâtes': 5, 'riz': 6})
Le conteneur Counter
Le conteneur Counter est une sous-classe de dictionnaire qui permet de compter des objets hachables.
C’est un dictionnaire avec comme clé les éléments et comme valeurs leur nombre.
Voici un exemple d'utilisation :
from collections import Counter
mon_compteur = Counter() # Compteur vide
print(mon_compteur) # Affiche : Counter()
mon_compteur = Counter('Docstring') #compteur avec un itérable
print(mon_compteur) # Affiche : Counter({'D': 1, 'o': 1, 'c': 1, 's': 1, 't': 1, 'r': 1, 'i': 1, 'n': 1, 'g': 1})
mon_compteur = Counter({'Vert': 4, 'Bleu': 2}) # un compteur avec un mapping
print(mon_compteur) # Affiche : Counter({'Vert': 4, 'Bleu': 2})
mon_compteur = Counter(chats=4, chiens=7)#un compteur avec key=valeur
print(mon_compteur) # Affiche : Counter({'chiens': 7, 'chats': 4})
Attention
Remarque : Si l'on demande une valeur qui n'est pas dans notre liste, il va retourner 0 et non pas une erreur de type "KeyError".
from collections import Counter
mon_compteur = Counter(['Python', 'Java'])
print(mon_compteur["PHP"]) # Clé inconnue, affichera : 0
Voici une liste des méthodes intéressantes du conteneur Counter :
-
elements() : retourne un itérateur (de type itertools.chain) de tous les éléments du compteur.
from collections import Counter mon_compteur = Counter(pomme=3, banane=2, carotte=0, avocat=-2) print(sorted(mon_compteur.elements()) # Affiche : ['banane', 'banane', 'pomme', 'pomme', 'pomme'] -
most_common(n) : retourne les n éléments les plus présents dans le compteur.
from collections import Counter print(Counter('Abracadabra!').most_common(3)) # Affiche : [('a', 4), ('b', 2), ('r', 2)] -
subtract(iterable ou mapping) : permet de soustraire des éléments d’un compteur.
from collections import Counter compteur_a = Counter(pomme=4, banane=2, carotte=0, avocat=-2) compteur_b = Counter(pomme=1, banane=2, carotte=3, avocat=4) compteur_a.subtract(compteur_b) print(compteur_a) # Affiche : Counter({'pomme': 3, 'banane': 0, 'carotte': -3, 'avocat': -6})
Le conteneur OrderedDict
Le conteneur OrderedDict est comme son nom l'indique, comme un dictictionnaire, à la différence qu'il se rappelle l’ordre d’entrée des valeurs.
Ainsi, si on itère dessus les données seront retournées dans l’ordre d’ajout dans notre dictionnaire.
Voici une liste des méthodes intéressantes du conteneur OrderedDict :
-
popitem(last=True) : fait sortir une paire clé-valeur de notre dictionnaire et si l’argument last est égal à True, alors les pairs seront retournées en LIFO (Last In, First Out) sinon ce sera en FIFO (First In, First Out).
-
move_to_end(key, last=True) : permet de déplacer une clé à la fin de notre dictionnaire si last est égal à True, sinon au début de notre dict.
Voici un exemple d'utilisation du conteneur OrderedDict et de ses méthodes :
from collections import OrderedDict
mon_dict=OrderedDict()
mon_dict['pomme'] = '1'
mon_dict['banane'] = '2'
mon_dict['carotte'] = '3'
mon_dict['avocat'] = '4'
print(mon_dict) # OrderedDict([('pomme', '1'), ('banane', '2'), ('carotte', '3'), ('avocat', '4')])
mon_dict.move_to_end('banane')
print(mon_dict) # OrderedDict([('pomme', '1'), ('carotte', '3'), ('avocat', '4'), ('banane', '2')])
mon_dict.move_to_end('banane',last=False)
print(mon_dict) # OrderedDict([('banane', '2'), ('pomme', '1'), ('carotte', '3'), ('avocat', '4')])
print(mon_dict.popitem(True)) # Affiche : ('avocat', '4')
print(mon_dict) # OrderedDict([('banane', '2'), ('pomme', '1'), ('carotte', '3')])
À noter
OrderedDict n'est aujourd'hui plus très utilisé étant donné que les dictionnaires sont ordonnés par défaut depuis Python 3.7.
Ce sont surtout ses méthodes qui peuvent être utiles pour redéfinir l'ordre du dictionnaire.
Le conteneur defaultdict
Le conteneur defaultdict permet de rassembler les informations dans les dictionnaires de manière rapide et concise. Il se comporte différemment d’un dictionnaire ordinaire.
En effet, au lieu de soulever une erreur de type KeyError sur une clé manquante, defaultdict va appeller la valeur de remplacement sans argument pour créer un nouvel objet.
Voici un exemple simple d'utilisation :
from collections import defaultdict
mon_defaultdict = defaultdict(list)
print(mon_defaultdict["missing"]) # Affiche : []
Mais on peut aussi l'utiliser en définissant une fonction pour gérer le comportement lorsqu'une clé est manquante :
from collections import defaultdict
def default_message():
return "la clé n'existe pas"
mon_obj_defaultdict = defaultdict(default_message)
mon_obj_defaultdict["cle1"] = "valeur1"
print(mon_obj_defaultdict["cle1"]) # Affiche: valeur1
print(mon_obj_defaultdict["c"]) # Affiche: la clé n'existe pas
Ou encore mieux, avec directement une fonction lambda !
from collections import defaultdict
mon_obj_defaultdict = defaultdict(lambda: "la clé n'existe pas")
mon_obj_defaultdict["cle1"] = "valeur1"
print(mon_obj_defaultdict["cle1"]) # Affiche: valeur1
print(mon_obj_defaultdict["c"]) # Affiche: la clé n'existe pas
Et enfin, lorsque la classe int est fournie comme fonction par défaut, c'est la valeur 0 qui est relevée :
from collections import defaultdict
mon_obj_defaultdict = defaultdict(int)
mon_obj_defaultdict["cle1"] = "valeur1"
print(mon_obj_defaultdict["cle1"]) # Affiche: valeur1
print(mon_obj_defaultdict["c"]) # Affiche: 0
Conclusion
J'espère qu'avec cet article vous y verrez un peu plus clair avec le module collections et ses conteneurs.
Le principe est assez simple, il est basé sur les conteneurs natifs de Python (listes, dictionnaires, tuple...) et vise à y rajouter des outils utiles pour accéder, parcourir et manipuler les données de ces derniers.
Ce module n'est pas le plus simple à maîtriser, mais une fois pris en main, il vous sera d'une grande aide pour optimiser votre manipulation des types de données en Python, alors pensez-y !