Files
training.python.beginner/documentation/02-language-basics-data.md
Steve Kossouho 086da10d79 Update chapters
Updated chapters 2, 6, 8, 9 and 11.
2025-07-11 21:36:41 +02:00

19 KiB
Raw Permalink Blame History

title, author
title author
Découvrir le langage - collections Steve Kossouho

Collections de données

Ce chapitre va présenter 4 types de données de base, qui permettent de manipuler plusieurs informations avec une seule variable :

  • Listes
  • Tuples
  • Ensembles
  • Dictionnaires

Listes

Type de collection de données le plus utile : list{.python}. Un objet de type liste peut contenir une séquence d'éléments de n'importe quel type en Python, y compris d'autres listes. Le contenu d'une liste est modifiable (ajouter, retirer des éléments…)

liste1 = [1, 2, 3, 4, 5, 6]  # éléments de types cohérents
liste2 = [1, 2, 3, "a", "b", "c"]  # pas de restriction sur les types
liste3 = [None, None, True, False]  # None n'est pas une valeur spéciale
liste4 = [[1, 2, 3], [4, 5, 6]]
liste5 = []  # liste vide. Équivalent à list()
liste6 = [liste1]  # Liste contenant 1 élément, qui est lui-même une liste

Il est possible d'exploiter une liste facilement; on peut ajouter de nouveaux éléments à la fin, les insérer à une position précise, retirer un élément ou encore récupérer un élément seul. Notez que les positions ([index]{.naming}) des éléments démarrent à zéro (0) et qu'une liste n'a jamais d'emplacement vide.

liste1 = [1, 2, 3, 4, 5, 6]
liste2 = list()  # Créer une nouvelle liste vide

# Infos sur la liste
length = len(liste1)  # Récupérer le nombre d'éléments
position = liste1.index(3)  # Renvoie l'index de la valeur 3, ou erreur si introuvable
nine_trouvable = 9 in liste1  # Renvoie si l'élément 9 existe dans la liste

# Récupérer des éléments
print(liste1[0])  # Affiche le premier élément si la liste est non vide

# Manipuler le contenu
liste1.append(7)  # Ajoute 7 comme nouvel élément à la fin
liste1.insert(0, 99)  # Insère 99 comme nouvel élément au tout début
liste1[0] = 98  # Remplace la valeur à l'index 0
liste1.remove(4)  # Enlève la première occurrence du nombre 4, ou erreur si introuvable
del liste1[3]  # Retire l'élément à l'index 3

Méthodes accessibles sur les listes


Erreurs d'accès

Accéder à un élément de liste n'existant pas génère une erreur (IndexError{.python}) et interrompt votre programme; il peut être utile d'utiliser la fonction len(){.python} pour tester que vous accédez à un indice valide.

liste1 = [1, 2, 3]
if len(liste1) >= 4:
    print(liste1[3])

Erreurs de méthodes

Les méthodes list.remove(){.python} et list.index(){.python} provoquent également une erreur (ValueError{.python}) si l'élément en argument est introuvable dans la liste. De la même façon, il peut être utile de tester qu'un élément est présent dans la liste avant d'essayer de l'en supprimer :

liste1 = ["fred", "denis", 14, 23, 32]
if 8 in liste1:  # Tester que 8 a été trouvé dans la liste
    liste1.remove(8)

Question : Qu'écririez-vous pour supprimer toutes les occurrences d'une valeur en utilisant list.remove(){.python} ?


Indices de liste négatifs

Il est également possible d'accéder à des éléments de liste non vide en utilisant des indices négatifs.

Dans ce cas, l'élément à l'index -1 d'une liste l est celui à l'index len(l) - 1{.python}, à savoir le dernier élément. L'élément à l'index -len(l){.python} sera celui à l'index 0, à savoir le premier élément. Tout index hors de la plage disponible provoquera une erreur de type IndexError{.python}.

animals = ["dog", "cat", "rabbit", "hamster", "parrot"]
print(animals[-1])  # affiche "parrot"
print(animals[-2])  # affiche "hamster"
print(animals[-5])  # affiche "dog"

Parcourir une liste

Comme on l'a vu avec la boucle for{.python}, utilisée avec range(){.python}, on peut utiliser la boucle for{.python} sur une liste :

prime_numbers = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31]
for number in prime_numbers:
    print(number)

La variable number{.python} déclarée pour la boucle contiendra tour à tour les valeurs 2{.python}, puis 3{.python}, puis 5{.python}, etc.


Opérations sur listes

Récupérer des portions ou partitions de listes est possible en Python avec la syntaxe suivante (slicing). Le comportement de cette syntaxe est basé sur le fonctionnement de la fonction range{.python}.

a = [10, 15, 20, 25, 30, 35, 40, 45, 50, 55]  # liste de base
b = a[0:5]  # copier dans une nouvelle liste les éléments aux index 0 à 4.
c = a[5:0]  # créer une nouvelle liste, mais ne rien y copier
d = a[:]  # copier de l'extrêmité gauche à droite incluses
e = a[::-1]  # copier tous les éléments à l'envers
f = a[::2]  # Tout parcourir 2 par 2
g = a[5:2:-1]  # L'indice de début est supérieur à l'indice de fin
h = a[2:5:-1]  # Le pas est négatif, start < end, renvoie une liste vide

Pourquoi créer des portions de listes ? Cela peut-être utile, par exemple, si vous souhaitez appliquer un calcul sur une petite partition d'un jeu de données.


Tuples

Tuple : Un tuple{.python} fonctionne quasiment trait pour trait comme une liste, à la différence qu'une fois que vous avez défini ses éléments, vous ne pouvez plus en changer. On parle de « collection immuable ».

a = (1, 2, 3)
b = ()  # ou tuple()
c = (1,)  # il vous faut au minimum une virgule
d = (None, "Salut", 15, 2.718)

Les méthodes de modification de contenu, telles que append{.python} etc. ne sont logiquement pas disponibles avec ce type : opérations des tuples


Ensembles (données uniques)

Set (ensemble) : Un set{.python} peut contenir plusieurs éléments et est modifiable au même titre qu'une liste, mais n'est pas une séquence; aucune notion d'ordre ou d'index. La collection garantit l'unicité des éléments, un élément ne pouvant apparaître qu'une fois au maximum dans votre objet.

a = {1, 1, 2, 3, 3, 3}  # équivaut à {1, 2, 3}, les valeurs étant uniques à la fin
b = {2, 4, 5, "hello", (1, 2, 3)}
c = set()  # obligatoire pour déclarer un set vide, sinon considéré dict

print(len(b))

a.add(9.5)  # ajoute la valeur 9.5 (float) au set
a.discard(9.5)  # retire la valeur 9.5, ou ne fait rien

d = a.intersection(b)  # récupérer un ensemble avec les éléments communs à a et b
e = a.union(b)  # récupérer un ensemble avec les éléments présents dans a ou b
...

Méthodes disponibles sur les ensembles


Opérations sur les ensembles


Bonus: Particularités des ensembles

Les ensembles sont des structures de données très efficaces. Techniquement, vous pouvez attendre une complexité en O(1) pour que Python sache si un élément est déjà présent ou pas; cela signifie qu'il faut plus ou moins le même nombre d'opérations pour trouver un élément, que votre set{.python} en contienne un seul ou un milliard.

L'algorithme et la structure de données interne à Python derrière cette efficacité se nomme [Table de hachage]{.naming}.


Tables de hachage

Une table de hachage fonctionne en trois temps:

  • On réserve des zones de mémoire d'avance (buckets), au départ vides;
  • lorsque l'on souhaite vérifier si un élément y est présent, on lui calcule une signature (hash(){.python});
  • la signature de l'objet permet de savoir quelle zone de mémoire consulter pour le trouver.
  • si la zone de mémoire est vide, c'est que l'objet n'était pas présent.

Exemple de hachage

Table de hachage


Hachage

La signature est un nombre calculé qui est sensé dépendre du contenu d'un objet. Python propose une fonction à cet effet, hash(){.python}, pour laquelle chaque classe propose sa propre implémentation. L'objectif d'un hash est simple; deux objets différents doivent avoir un hash différent. Dans Python ce dernier est un nombre entier sur 64 bits. Si deux objets a et b ont le même hash, ils seront généralement considérés comme équivalents et donc en collision (dans ce cas, a == b{.python}). S'ils ne sont pas considérés équivalents, il n'y a pas de collision.


Hachage en général dans Python

Entre deux lancements d'un interpréteur Python, le hash de la majorité des objets est différent; l'algorithme utilise une valeur aléatoire pour générer les hashs, de façon qu'il soit impossible de créer un dictionnaire de signatures dédié à créer des collisions.

Le hachage est en général imprévisible pour les valeurs suivantes:

  • str{.python} non vides
  • float{.python} non équivalents à un int{.python}
  • None{.python}
  • tuple{.python}

Hashes prévisibles

Le hash est par contre prévisible pour les valeurs suivantes:

  • 0{.python}, 0.0{.python}, False{.python} et ""{.python} ont un hash de 0{.python} mais ""{.python} n'est pas équivalent;
  • 1{.python}, 1.0{.python} et True{.python} ont un hash de 1{.python} et sont tous en collision.
  • int{.python}, où le hash est identique au nombre...

Les objets int{.python} ont généralement un hash(){.python} identique, sauf cas suivants:

  • -1{.python} a un hash de -2{.python} car la valeur -1{.python} est un code d'erreur;
  • 2 ** 61 - 2{.python} est le dernier nombre positif avec un hash identique;
  • à partir de 2 ** 61 - 1{.python}, le hash revient à 0 etc.
  • le même algorithme fonction sur les entiers négatifs, mais avec des hashes négatifs.

Hashes impossibles

L'algorithme de la table de hachage nécessite qu'un objet stocké dans un bucket possède en permanence un hash correspondant à son bucket. Cela pose un problème avec les objets modifiables à tout moment, tels que les list{.python}, set{.python} ou encore les dict{.python}.

La solution adoptée par Python consiste à interdire le calcul de signature desdits objets. Les tuple{.python} demeurent des données valides, puisque nous avons la garantie de ne jamais pouvoir changer leur contenu, et par là-même leur signature.

# La fonction hash est disponible par défaut en Python
print(hash([1, 2, 3]))  # Provoque une exception
print(hash({1, 2, 3}))  # Même problème

Dictionnaires : associations entre clés et valeurs

Dictionnaires : (dict{.python}) C'est un ensemble d'associations où l'on définit [clés]{.naming} et [valeurs]{.naming}. C'est le même fonctionnement qu'un dictionnaire lexicographique, où, lorsque vous avez le mot (la clé), vous retrouvez la définition s'il y en a une (la valeur). D'autres langages ont des structures similaires et appellent ça des HashMap{.java} ou des object{.javascript}.

a = {"server1": "192.168.1.2", "server2": "192.168.1.3", "server3": "192.168.1.5"}  # serveurs et adresses IP
b = {8: "Mme Garnier", 10: "M. Dubois", 11: "Mlle Yousfi", 12: "Mme Préjean"}  # rendez-vous horaires
d = {1.2: "flottant", True: "booléen", None: "rien", (1, 2, 3): "tuple"}  # clés de plusieurs types
c = {}  # ou dict(), ceci est un dictionnaire vide

print(a["server1"])  # affiche "192.168.1.2"
print(b[10])  # affiche "M. Dubois"
print(b[9])  # provoque une erreur
print(9 in b)  # Vérifier que 9 fait partie des clés de b
print(9 in b.values())  # Vérifier que 9 fait partie des valeurs de b

Les dictionnaires ont les mêmes contraintes que les ensembles uniquement au niveau de leurs clés; vous pouvez uniquement utiliser des valeurs de types immuables, tels que int{.python}, float{.python}, tuple{.python}...


Parcourir un dictionnaire

Il est possible de parcourir un dictionnaire avec une boucle for{.python}. Dans ce cas, ce sont uniquement les clés du dictionnaire qui sont parcourues. Mais il existe des variantes assez pratiques pour parcourir un dictionnaire :

profils = {
    "Jérémy": (25, "M", "Lille"),
    "Hélène": (30, "F", "Ambérieu-en-Bugey"),
    "Gwladys": (35, "F", "Nyons"),
    "Thierry": (40, "M", "Montceau-les-Mines"),
    "Carole": (45, "F", "Saint-Rémy-lès-Chevreuse"),
}

for prenom in profils:
    print(prenom)  # affiche uniquement une clé
    print(profils[prenom])  # retrouve la valeur associée à la clé

Si vous parcourez une collection dont tous les éléments sont eux-mêmes des séquences de n éléments, vous pouvez les dépaqueter (unpacking) :

# Avancé, unpacking via la méthode `items`
profils = {...}

for item in profils.items():  # a.items() renvoie ((clé1, valeur1), (clé2, valeur2), …)
    print(item)  # est un tuple

for key, value in profils.items():
    # on peut utiliser 2 variables de boucle si chaque élément parcouru est 
    # une séquence de taille 2. Merci l'unpacking !
    print(key, value)  

Manipuler un dictionnaire

Pour changer la valeur associée à une clé existante, ou par la même occasion, associer une valeur à une nouvelle clé, il suffit d'écrire :

populations = {"Bordeaux": 250000, "Cherbourg": 80000}
print(populations)

# Modifier le contenu
populations["Marseille"] = 800000
populations["Bordeaux"] = 90
print(populations["Bordeaux"])  # Affiche 90

# On peut supprimer du dictionnaire une association en écrivant
del populations["Marseille"]  # plus de clé pour Marseille !
print("Marseille" in populations)  # renvoie si la clé "Marseille" existe

Extra: méthodes de dictionnaires

La méthode get(key, default){.python} des dictionnaires renvoie la valeur associée à une clé. Si l'on ne passe pas de valeur pour l'argument default{.python}, pour une clé introuvable, la méthode renvoie None{.python} (au lieu de planter comme lorsqu'on écrit dict[key]{.python}).

populations = {"Paris": 2.6e6, "Marseille": 8e5, "Lyon": 5e5, "Bordeaux": 2.5e5}
print(populations.get("Tarbes"))  # renvoie None
print(populations.get("Mulhouse", -1))  # renvoie -1
print(populations.get("Marseille", -1))  # renvoie 800 000.0

Si l'on passe une valeur pour l'argument default{.python}, alors, si la clé n'a pas été trouvée, la méthode renvoie la valeur de repli que vous avez définie.


Compréhensions de listes, tuples etc.

Déclarer des listes, tuples, sets et dictionnaires avec la syntaxe de compréhension :

a = [x * 2 for x in range(100)]  # tous les nombres pairs de 0 à 198
b = [x * 2 for x in range(100) if x != 10]  # nombres pairs de 0 à 198 sauf 20
c = (x for x in range(100))  # pas un tuple mais un générateur, nombres de 0 à 99
d = {x / 2 for x in range(100)}  # on peut faire pareil avec les sets
pre = {"Jon": None, "Pam": None, "Mel": None, "Kim": None}
e = {k: pre[k] for k in pre}  # on copie pre

Récapitulatif des collections

Type Propriétés
list [...,]{.python}. Séquence dont le contenu est modifiable (ajout, suppression).
tuple (...,){.python}. Séquence dont le contenu est gelé après déclaration. Utilisable comme élément d'un set.
set {...,}{.python}. Déduplique les éléments et permet des opérations entre ensembles
(union, intersection etc.)
dict {..:..,}{.python}. Structure de recherche rapide où l'on peut associer des valeurs quelconques à des identifiants.

Usages d'exemple

Le tuple{.python} n'est jamais très important, et il sera souvent plus pratique de travailler sur des listes. Les seuls cas de figure qui obligent à utiliser des tuple{.python} sont les cas où vous voulez ajouter une séquence comme élément d'un set ou comme clé d'un dict{.python}.

  • list{.python} : si vous avez des données séquentielles à stocker, par exemple lues depuis un fichier.
  • set{.python} : si vous souhaitez enlever des doublons, connaître le nombre d'éléments uniques.
  • dict{.python} : si vous avez besoin d'une "table de référence" pour stocker des données que vous allez rechercher fréquemment.

Chaînes de caractères


Opérations sur les chaînes de caractères

Nous n'avons pas vu grand chose sur les chaînes de caractères, mais certaines informations peuvent être fréquemment utiles aux développeurs :

number = 2_643
print(f"Le serveur qui doit être vérifié aujourd'hui est le numéro {number}")
print(f"Formatage de la variable : {number:f}")  # affiché comme flottant (6 chiffres après la virgule)

Les chaînes de caractères et leurs méthodes

chaine = "Bonjour"
print(chaine.upper(), chaine.lower())
print(chaine.center(50, " "))  # Centre la chaîne originale sur 50 caractères

Méthodes qui renvoient une copie modifiée d'une chaîne


Méthodes fréquemment utiles sur les chaînes de caractères

  • upper(){.python} : renvoie une copie en majuscules
  • lower(){.python} : renvoie une copie en minuscules
  • strip(){.python} : retire les espaces aux extrêmités
  • replace(old, new, count=None){.python} : remplace old par new
  • index(sub, start=None){.python} : renvoie la position de sub
  • sub in text{.python} : renvoie si sub est inclus dans text
  • split(sep=None){.python} : découpe en liste autour du séparateur, par défaut autour des espaces
  • str.join(iterable){.python} : sert de séparateur et joint une liste de chaînes

chaine = "Bonjour, nous sommes le 17 juillet 2063."
words = chaine.split()  # renvoie une liste de chaînes autour des espaces
rejoin = " ".join(words) # renvoie une chaîne en insérant l'espace comme séparateur
print(chaine.upper(), chaine.lower(), rejoin)
print("17 juillet" in chaine)

Exemple de str.split(){.python} et str.join(){.python}