Les collections
Introduction
Les collections permettent de regrouper des données ou des objets afin de les parcourir, de les rechercher, d’y accéder et de les manipuler. Python propose plusieurs types de collections intégrés au langage : les listes, les tuples, les ensembles et les dictionnaires.
Pour un développeur C#, leurs usages rappellent ceux de List<T>, des tuples, de HashSet<T> et de Dictionary<TKey, TValue>. Leurs comportements présentent toutefois quelques différences.
Les listes
Présentation
Une liste (list) est une collection ordonnée et modifiable, qui accepte les doublons. Chaque élément est accessible par son indice, le premier élément ayant l’indice 0. Elle se rapproche de la collection List<T> en C#.
Créer une liste
Une liste peut être créée avec des crochets :
Pour créer une liste vide :
Une liste peut contenir des éléments de types différents. Cependant, conserver des éléments de même type facilite la manipulation des données. Nous pouvons préciser le type attendu des éléments grâce à une annotation :
Cette annotation est utilisée par les outils d’analyse de code, tels que Pylance ou mypy, pour détecter les incohérences de types. Elle ne contrôle pas les types des éléments.
Accéder aux éléments
Les indices positifs ou nuls permettent d’accéder aux éléments depuis le début de la liste. Les indices négatifs permettent d’y accéder à partir de la fin : -1 désigne le dernier élément, -2 l’avant-dernier, et ainsi de suite.
names = ["Baptiste", "Mathieu", "Damien"]
print(names[0]) # Baptiste
print(names[1]) # Mathieu
print(names[-2]) # Mathieu
print(names[-1]) # Damien
Un indice situé en dehors des limites de la liste entraîne la levée d’une exception de type IndexError.
Modifier une liste
Nous pouvons remplacer un élément, en ajouter ou en supprimer :
names = ["Baptiste", "Mathieu", "Damien"]
names[1] = "James"
names.append("Karl")
names.insert(0, "Ludwig")
names.remove("Damien")
print(names)
# ['Ludwig', 'Baptiste', 'James', 'Karl']
Les principales opérations sont :
| Opération | Description |
|---|---|
append(value) |
Ajoute un élément à la fin |
extend(values) |
Ajoute tous les éléments d’une autre collection |
insert(index, value) |
Insère un élément à une position donnée |
remove(value) |
Supprime la première occurrence d’une valeur ; lève une exception de type ValueError si elle est absente |
pop(index) |
Supprime et renvoie l’élément à l’indice indiqué. Sans indice, retire le dernier |
clear() |
Supprime tous les éléments |
Extraire une partie d’une liste
La syntaxe de découpage, appelée slicing, permet d’extraire une partie d’une liste :
numbers = [10, 20, 30, 40, 50]
print(numbers[1:4]) # [20, 30, 40]
print(numbers[:3]) # [10, 20, 30]
print(numbers[2:]) # [30, 40, 50]
Dans numbers[start:end], l’indice de début est inclus et celui de fin est exclu. Lorsque l’indice de début est omis, la nouvelle liste contient les éléments à partir du premier. Lorsque l’indice de fin est omis, elle contient les éléments jusqu’au dernier. Le résultat est une nouvelle liste ; la liste d’origine n’est pas modifiée.
Les tuples
Présentation
Un tuple est une collection ordonnée et immuable, qui accepte les doublons. Une fois créé, nous ne pouvons pas supprimer ou remplacer les éléments qu'il contient, ni en ajouter.
Créer et utiliser un tuple
Voici un tuple (tuple) représentant les coordonnées d’un point :
Nous pouvons décomposer le tuple pour affecter ses valeurs à plusieurs variables :
Cette opération est appelée déballage (unpacking). Elle correspond à la déconstruction des tuples en C#.
Pour créer un tuple contenant un seul élément, une virgule est nécessaire :
Sans cette virgule, (10) représente simplement le nombre 10 entre parenthèses.
Comprendre l’immutabilité
L’immutabilité concerne la structure du tuple : nous ne pouvons pas ajouter, supprimer ou remplacer ses éléments. En revanche, un objet modifiable contenu dans le tuple peut toujours être modifié :
data = ("Équipe", ["Baptiste", "Mathieu"])
data[1].append("Damien")
print(data)
# ('Équipe', ['Baptiste', 'Mathieu', 'Damien'])
Un tuple Python ne possède pas exactement le même comportement qu’un tuple C#, dont les éléments peuvent être modifiés.
Les ensembles
Présentation
Un ensemble (set) est une collection modifiable d’éléments uniques. Il ne garantit pas l’ordre des éléments et ne permet pas d’y accéder via un indice.
Il correspond à la classe HashSet<T> en C#. Il convient pour éliminer les doublons, tester l’appartenance d’une valeur ou comparer plusieurs groupes de données.
Créer un ensemble
Un ensemble peut être créé en plaçant ses éléments entre accolades :
La valeur "C#" n’est conservée qu’une seule fois.
Pour créer un ensemble vide, nous utilisons set() :
Les accolades vides {} créent un dictionnaire, et non un ensemble.
Modifier un ensemble
La méthode add(<item>) permet d’ajouter un élément. S’il est déjà présent, l'élément n'est pas ajouté :
La méthode discard(<item>) supprime un élément s’il existe. La méthode remove(<item>) permet aussi de le supprimer, mais lève une exception de type KeyError s’il est absent.
Les opérations entre ensembles
Les ensembles permettent de réaliser des opérations d’union, d’intersection et de différence :
first = {"C#", "Python", "Java"}
second = {"Python", "JavaScript"}
union = first | second
intersection = first & second
difference = first - second
symmetric_difference = first ^ second
| Opération | Résultat |
|---|---|
first \| second |
Éléments présents dans au moins un des deux ensembles. |
first & second |
Éléments communs aux deux ensembles. |
first - second |
Éléments présents dans first, mais absents de second. |
first ^ second |
Éléments présents dans un seul des deux ensembles. |
Les éléments hachables
Les éléments d’un ensemble doivent être "hachables" : Python doit pouvoir calculer un code de hachage (hashcode) stable (pendant la durée de vie de l'objet) permettant de les retrouver. Deux éléments considérés comme égaux ont le même code de hachage, mais deux éléments ayant le même code ne sont pas nécessairement égaux.
Un code de hachage n’est pas un identifiant unique : des valeurs différentes peuvent produire le même code. Cela s’appelle une collision.
Voici un exemple en Python :
Les nombres -1 et -2 sont différents, mais leurs codes de hachage sont identiques.
Un ensemble utilise le code de hachage pour rechercher un élément, puis une comparaison d’égalité pour distinguer les éléments ayant le même code. Il conserve donc bien les deux valeurs -1 et -2 :
Les nombres et les chaînes de caractères sont hachables. Les listes et les dictionnaires ne le sont pas et ne peuvent donc pas être directement ajoutés à un ensemble.
Python propose également frozenset, une version immuable d’un ensemble. Un frozenset se crée à partir d’une collection, par exemple une liste :
languages = frozenset(["C#", "Python", "C#", "Java"])
print(len(languages)) # 3
print("Python" in languages) # True
Comme avec les ensembles set, les doublons sont éliminés. En revanche, nous ne pouvons pas ajouter ou supprimer d’éléments : les méthodes add(<item>), remove(<item>) et discard(<item>) ne sont pas disponibles.
Les dictionnaires
Présentation
Un dictionnaire (dict) associe des valeurs à des clés uniques. Il est modifiable et conserve l’ordre d’insertion des clés. Il se rapproche de la classe Dictionary<TKey, TValue> en C#.
Créer un dictionnaire
Un dictionnaire peut être créé avec des accolades, chaque clé étant séparée de sa valeur par deux-points. Dans cet exemple, l’annotation dict[int, str] indique que les clés attendues sont des entiers et les valeurs des chaînes de caractères :
Les clés doivent être hachables. Les valeurs peuvent être des données ou des objets de n’importe quel type.
Accéder aux valeurs
Nous pouvons retrouver une valeur à partir de sa clé :
Si la clé est absente, cet accès lève une exception de type KeyError.
La méthode get permet de fournir une valeur par défaut :
Sans valeur par défaut explicite, la méthode get renvoie None lorsque la clé est absente du dictionnaire.
Modifier un dictionnaire
L’affectation ajoute une association clé - valeur si la clé est absente, ou remplace sa valeur si elle existe déjà. L’instruction del permet de supprimer une association :
products[104] = "Casque" # Ajout
products[102] = "Souris USB" # Modification
del products[103] # Suppression
Ce comportement est similaire à celui de l’indexeur dictionary[key] en C#. Cependant, la méthode Add de Dictionary<TKey, TValue> en C# lève une exception si la clé existe déjà.
Si la clé à supprimer est absente, l’instruction del lève une exception de type KeyError.
Parcourir un dictionnaire
Par défaut, un traitement itératif for sur un dictionnaire parcourt ses clés :
La méthode items() permet de récupérer les clés et les valeurs :
Les méthodes keys() et values() donnent respectivement accès aux clés et aux valeurs du dictionnaire.
Les opérations communes
Obtenir le nombre d’éléments
La fonction len() renvoie le nombre d’éléments d’une collection :
Elle correspond, selon le type C# considéré, à l’utilisation de Count ou de Length.
Vérifier la présence d’un élément
Les opérateurs in et not in permettent de tester l’appartenance :
names = ["Baptiste", "Mathieu"]
print("Baptiste" in names) # True
print("Damien" not in names) # True
Sur un dictionnaire, ces opérateurs recherchent une clé :
Parcourir une collection
Une boucle for permet de parcourir les éléments, comme foreach en C# :
Pour numéroter les éléments pendant le parcours, nous utilisons enumerate(). Le compteur commence à 0 et correspond ici à l’indice dans la liste :
Les collections d’objets
Les collections peuvent contenir des instances de classes :
class Product:
def __init__(self, name: str, price: float):
self.name = name
self.price = price
products: list[Product] = [
Product("Clavier", 69.90),
Product("Souris", 29.90),
Product("Écran", 249.90)
]
for product in products:
print(f"{product.name} : {product.price:.2f} €")
Nous pouvons aussi utiliser un dictionnaire pour retrouver un objet à partir de son identifiant :
products_by_id: dict[int, Product] = {
101: Product("Clavier", 69.90),
102: Product("Souris", 29.90)
}
product = products_by_id[101]
Filtrer et transformer les données
Les compréhensions de liste
Une compréhension de liste permet de créer une nouvelle liste en parcourant une collection, en transformant éventuellement ses éléments et en les filtrant selon une condition.
Pour sélectionner les produits dont le prix est inférieur à 100 euros :
Cette opération rappelle l’utilisation de la méthode Where() suivie de ToList() en LINQ lorsqu'on développe en C#.
Pour extraire les noms des produits :
Cette transformation se rapproche de Select() suivi de ToList() en LINQ lorsqu'on développe en C#.
Nous pouvons combiner filtrage et transformation :
Ces compréhensions créent une nouvelle liste.
Les compréhensions d’ensemble et de dictionnaire
Le même principe s’applique aux ensembles. Cet exemple crée un ensemble contenant les noms des produits, sans doublons :
Une compréhension de dictionnaire permet de créer des associations clé-valeur. Ici, chaque nom de produit est associé à son prix :
Si plusieurs produits portent le même nom, seul le prix du dernier produit rencontré est conservé pour cette clé.
Trier les données
La fonction sorted() crée une nouvelle liste triée :
numbers = [30, 10, 20]
ordered_numbers = sorted(numbers)
print(ordered_numbers) # [10, 20, 30]
print(numbers) # [30, 10, 20]
Pour trier des objets, le paramètre key reçoit une fonction qui renvoie la valeur utilisée pour le tri. Dans cet exemple, l’expression lambda product: product.price permet de trier les produits par prix croissant :
En ajoutant le paramètre reverse=True, les produits sont triés par prix décroissant.
La méthode sort() trie une liste existante et renvoie None :
Copier une liste
Affecter une liste à une autre variable ne crée pas une nouvelle liste :
first = ["Baptiste", "Mathieu"]
second = first
second.append("Damien")
print(first) # ['Baptiste', 'Mathieu', 'Damien']
Les deux variables désignent le même objet, comme deux variables C# référençant la même instance de List<T>.
Pour créer une nouvelle liste contenant les mêmes éléments, nous utilisons la méthode copy() :
Il s’agit d’une copie superficielle : les deux listes sont distinctes, mais leurs éléments désignent les mêmes objets. Si un objet contenu dans ces listes est modifiable, sa modification sera visible depuis les deux listes.
Choisir la collection appropriée
| Besoin | Collection |
|---|---|
| Gérer une suite d’éléments modifiable | list |
| Regrouper des valeurs dans une collection ordonnée et immuable | tuple |
| Gérer des valeurs uniques et comparer des groupes | set |
| Gérer une collection immuable d’éléments uniques | frozenset |
| Retrouver une valeur ou un objet à partir d’une clé | dict |
La bibliothèque standard propose aussi des collections spécialisées, notamment avec la classe collections.deque pour les files :
from collections import deque
tasks = deque(["Importer", "Vérifier"])
tasks.append("Enregistrer") # Ajout à la fin
task = tasks.popleft() # Retrait au début
print(task) # Importer
print(list(tasks)) # ['Vérifier', 'Enregistrer']
Et la classe collections.Counter pour compter les occurrences :
from collections import Counter
languages = ["C#", "Python", "C#", "Java", "Python", "C#"]
counts = Counter(languages)
print(counts["C#"]) # 3
print(counts["Python"]) # 2
print(counts["Java"]) # 1
print(counts["JavaScript"]) # 0
Pour les tableaux numériques et le calcul scientifique, NumPy propose des structures adaptées. Pour les données tabulaires organisées en lignes et en colonnes, pandas fournit notamment le type DataFrame. NumPy et pandas sont des bibliothèques tierces.