backend / python
Listes : création et méthodes
Explication
Ce que vous allez apprendre
- Manipuler une liste avec les bonnes méthodes (
append,insert,pop,sort...) et connaître leur coût - Comprendre pourquoi une liste contient des références, pas des copies des objets qu'elle stocke
- Distinguer copie superficielle (
.copy()) et copie profonde (copy.deepcopy()) pour éviter les bugs partagés - Éviter le piège classique de
[[0]*3]*3qui crée trois références vers la même sous-liste - Reconnaître quand
collections.dequeest plus adapté qu'une liste classique
Dans quel contexte ?
Un développeur initialise une grille de jeu avec plateau = [[0] * 3] * 3 pour un morutier, joue un coup en écrivant plateau[0][0] = 1, et découvre avec stupeur que la même valeur apparaît aussi en plateau[1][0] et plateau[2][0]. Ce bug, extrêmement courant chez les débutants, vient du partage de référence entre les trois "lignes" du plateau — cette leçon explique exactement pourquoi, et comment l'éviter avec une compréhension de liste.
La structure de données la plus utilisée
Une liste est une séquence ordonnée d'éléments qu'on peut modifier après sa création : ajouter, retirer, réordonner. C'est l'équivalent d'un classeur dont on peut changer le contenu et l'ordre des pages à volonté, contrairement à un livre relié qu'on ne peut que lire. Cette mutabilité est à la fois sa plus grande force et sa principale source de bugs si on ne la comprend pas bien.
Référence, pas copie
Le piège le plus classique concerne le partage de référence : quand on écrit mauvais = [[0]*3]*3, on ne crée pas trois listes indépendantes mais trois étiquettes pointant vers la même sous-liste. Modifier une case en modifie donc trois. C'est la même logique que pour les variables : une liste ne contient pas ses éléments comme des cases physiques, elle contient des références vers eux. D'où l'importance de distinguer copie superficielle (.copy(), qui duplique le conteneur mais pas les objets à l'intérieur) et copie profonde (copy.deepcopy(), qui duplique récursivement tout).
| Méthode | Ce qu'elle copie | Risque |
|---|---|---|
liste_b = liste_a | Rien, juste une nouvelle étiquette | Les deux noms pointent vers la même liste |
liste_a.copy() / liste_a[:] | Le conteneur, pas les sous-objets | Les sous-listes/dicts restent partagés |
copy.deepcopy(liste_a) | Tout, récursivement | Aucun, mais plus lent |
Piège fréquent
[[0] * 3] * 3 ne crée pas trois sous-listes indépendantes : l'opérateur * duplique la même référence trois fois. Utilisez toujours [[0] * 3 for _ in range(3)] pour obtenir des sous-listes réellement distinctes.
Choisir le bon outil selon la complexité
Toutes les opérations n'ont pas le même coût : ajouter en fin de liste est quasi instantané, mais insérer en début de liste oblige Python à décaler tous les éléments existants. Pour des insertions/suppressions fréquentes en tête de séquence, collections.deque est bien plus adapté qu'une liste classique.
Le socle des structures suivantes
Comprendre les listes en profondeur prépare directement aux tuples et aux sets (leçon suivante), qui partagent la même logique de séquence mais avec des garanties différentes : l'immuabilité pour les uns, l'absence de doublons pour les autres.
Commandes & code
Listes
Sequences mutables et ordonnees, la structure la plus utilisee en Python.
# Creation
vide = []
nombres = [1, 2, 3, 4, 5]
mixte = [1, "deux", 3.0, [4, 5]] # types heterogenes autorises
# Indexation et slicing
premier = nombres[0]
dernier = nombres[-1]
sous_liste = nombres[1:3] # [2, 3]
copie = nombres[:] # copie superficielle
# Methodes de mutation
nombres.append(6) # ajoute a la fin : O(1) amorti
nombres.insert(0, 0) # insere a un index : O(n)
nombres.extend([7, 8]) # concatene une autre iterable
nombres.remove(0) # supprime la 1ere occurrence de la valeur
dernier_pop = nombres.pop() # retire et retourne le dernier element
nombres.pop(0) # retire a un index precis
nombres.sort() # tri en place
nombres.sort(reverse=True) # tri descendant
nombres.reverse() # inverse en place
# sorted() : renvoie une nouvelle liste (ne mute pas l'originale)
personnes = [("Ana", 30), ("Bo", 25), ("Cy", 35)]
par_age = sorted(personnes, key=lambda p: p[1])
par_age_desc = sorted(personnes, key=lambda p: p[1], reverse=True)
# Recherche et comptage
print(3 in nombres)
print(nombres.index(3))
print(nombres.count(3))
# Concatenation et repetition
a = [1, 2] + [3, 4]
b = [0] * 5 # [0, 0, 0, 0, 0]
# ATTENTION au piege classique : multiplier des listes de listes
mauvais = [[0] * 3] * 3 # les 3 sous-listes sont LA MEME reference
mauvais[0][0] = 9
print(mauvais) # [[9,0,0],[9,0,0],[9,0,0]] -- piege !
bon = [[0] * 3 for _ in range(3)] # chaque sous-liste est independante
bon[0][0] = 9
print(bon) # [[9,0,0],[0,0,0],[0,0,0]]
# Copie superficielle vs copie profonde
import copy
originale = [[1, 2], [3, 4]]
superficielle = originale.copy() # copie le conteneur, pas les sous-objets
profonde = copy.deepcopy(originale) # copie recursive complete
superficielle[0].append(99)
print(originale) # [[1, 2, 99], [3, 4]] -- impactee !
print(profonde) # inchangee
# Listes comme piles (stack) : append/pop en O(1)
pile = []
pile.append(1)
pile.append(2)
print(pile.pop()) # 2 (LIFO)
# Pour une file (queue) performante, preferer collections.deque
from collections import deque
file = deque([1, 2, 3])
file.appendleft(0) # O(1), contrairement a list.insert(0, ...)
file.popleft()| Opération | Complexité |
|---|---|
list[i] (accès) | O(1) |
list.append(x) | O(1) amorti |
list.insert(0, x) | O(n) |
list.pop() | O(1) |
list.pop(0) | O(n) |
x in list | O(n) |
Résumé
- Les listes sont mutables, ordonnées, et acceptent des types mixtes.
[[0]*3]*3est un piège classique de partage de référence : préférer une compréhension.- Pour des insertions/suppressions fréquentes en tête, utiliser
collections.deque.
Exercices pratiques
Mission : réparer un plateau de morpion truqué par la mémoire
Objectif : Corriger un bug de partage de référence dans une grille de jeu, et choisir la bonne stratégie de copie pour un système de sauvegarde.
Contexte
Un jeu de morpion initialise son plateau avec plateau = [[0] * 3] * 3. Dès qu'un joueur pose un pion en plateau[0][0], les cases plateau[1][0] et plateau[2][0] changent aussi, sans qu'aucune ligne de code ne les touche directement. L'équipe soupçonne un bug d'affichage, mais le problème est ailleurs.
Tu dois localiser la cause réelle, corriger l'initialisation, puis mettre en place une sauvegarde du plateau qui ne soit jamais affectée par les coups joués ensuite.