Les listes

Informatique — Python, chapitre 3

Une variable retient une valeur. Pour en retenir mille — les revenus d’un échantillon, les années d’une série — il faut une structure qui les rassemble. En Python, c’est la liste.

Elle occupe la place qu’a le vecteur en R, avec une différence majeure : elle n’est pas vectorisée. Multiplier une liste par 2 ne double pas ses valeurs. C’est précisément ce manque qui justifiera NumPy au chapitre 8.

3.1 Créer une liste

v = [4, 8, 15, 16, 23, 42] print(v) print(len(v)) vide = [] print(len(vide))

3.1.1 Une liste accepte tous les types

melange = [1, "deux", 3.0, True, [5, 6]] print(melange) print(type(melange[1])) print(melange[4][0]) # une liste dans une liste

Cette souplesse distingue Python de R, où un vecteur convertit tout au type le plus général. Ici, chaque élément garde son type — et le prix de cette souplesse est la lenteur du calcul, que NumPy corrigera.

3.2 Extraire

3.2.1 Par position

Les règles sont exactement celles des chaînes du chapitre précédent.

v = [4, 8, 15, 16, 23, 42] print(v[0]) # le premier print(v[2]) # le troisieme print(v[-1]) # le dernier print(v[-2]) # l'avant-dernier

3.2.2 Les tranches

v = [4, 8, 15, 16, 23, 42] print(v[1:4]) # du 1er au 3e indice print(v[:3]) # les trois premiers print(v[3:]) # a partir du 4e print(v[-2:]) # les deux derniers print(v[::2]) # un sur deux print(v[::-1]) # a l'envers
AstuceUne tranche ne provoque jamais d’erreur de dépassement
v = [4, 8, 15]
print(v[10])       # IndexError
print(v[1:10])     # renvoie [8, 15], sans se plaindre

Un indice hors limites déclenche une erreur ; une tranche hors limites s’arrête simplement où la liste s’arrête.

C’est commode, mais cela masque parfois un bug : une tranche qui renvoie moins d’éléments que prévu ne le signale pas.

3.3 Modifier

3.3.1 Une liste se modifie sur place

C’est la différence essentielle avec les chaînes.

v = [4, 8, 15] v[0] = 100 # on remplace un element print(v) mot = "abc" # mot[0] = "z" # TypeError : une chaine est immuable

3.3.2 Ajouter et retirer

v = [4, 8, 15] v.append(16) # ajouter a la fin v.insert(1, 99) # inserer a la position 1 v.extend([23, 42]) # ajouter plusieurs elements print(v) v.remove(99) # retirer la premiere occurrence d'une VALEUR dernier = v.pop() # retirer et renvoyer le dernier print(v, dernier)
Avertissementappend et extend ne font pas la même chose
a = [1, 2]
a.append([3, 4])      # [1, 2, [3, 4]]  : un seul element ajoute
b = [1, 2]
b.extend([3, 4])      # [1, 2, 3, 4]    : deux elements ajoutes

append ajoute un élément, quel qu’il soit — même une liste entière. extend ajoute chaque élément d’un ensemble.

Essayez les deux dans la console : le résultat est très différent.

3.3.3 Les méthodes qui ne renvoient rien

v = [4, 8, 15] resultat = v.append(16) print(resultat) # None ! print(v) # mais la liste a bien change
ImportantNone, le piège du débutant
v = v.append(16)       # v vaut maintenant None : la liste est perdue

Les méthodes qui modifient une liste — append, extend, insert, remove, sort, reverse — travaillent sur place et renvoient None.

Écrivez v.append(16) seul, sans affectation. C’est l’inverse exact des chaînes du chapitre 2, où il fallait impérativement réaffecter.

3.4 Trier

v = [15, 4, 42, 8] trie = sorted(v) # renvoie une NOUVELLE liste print(trie, v) # v est intact v.sort() # modifie v SUR PLACE print(v) v.sort(reverse=True) # ordre decroissant print(v)

sorted renvoie, sort modifie. Retenez la règle générale : en Python, une fonction renvoie une valeur, une méthode de liste modifie souvent sur place.

villes = [("Tanger", 45.2), ("Rabat", 62.1), ("Fes", 38.7)] par_pib = sorted(villes, key=lambda x: x[1], reverse=True) print(par_pib)

L’argument key reçoit une fonction indiquant sur quoi trier. lambda x: x[1] est une fonction anonyme — « prends l’élément, renvoie sa deuxième composante ». Le chapitre 6 y revient.

3.5 Le piège de la copie

C’est le point le plus important du chapitre, et celui qui coûte le plus cher quand on l’ignore.

a = [1, 2, 3] b = a # ceci n'est PAS une copie b.append(4) print(a) # a a change aussi !
Importantb = a ne copie pas, il donne un second nom

a et b désignent le même objet en mémoire. Modifier l’un modifie l’autre, puisqu’il n’y en a qu’un.

Pour obtenir une vraie copie :

b = a.copy()        # ou : b = a[:]  ou  b = list(a)

Ce comportement vaut pour tous les objets modifiables de Python : listes, dictionnaires, et les tableaux NumPy du chapitre 8. Il est à l’origine de bugs particulièrement déroutants, où une variable change sans qu’aucune ligne ne semble la toucher.

a = [1, 2, 3] b = a.copy() b.append(4) print(a) # intact print(b)

3.6 Les compréhensions de liste

C’est l’écriture la plus caractéristique de Python, et la plus élégante.

v = [4, 8, 15, 16, 23, 42] doubles = [x * 2 for x in v] print(doubles) grands = [x for x in v if x > 15] print(grands) racines = [round(x ** 0.5, 2) for x in v] print(racines)

La lecture se fait de gauche à droite : pour chaque x de v, garde x * 2. Avec un if à la fin, on filtre.

C’est ainsi qu’on compense l’absence de vectorisation : [x * 2 for x in v] remplace le v * 2 de R.

v = [4, 8, 15, 16] # version longue doubles = [] for x in v: doubles.append(x * 2) # version compacte, strictement equivalente doubles = [x * 2 for x in v] print(doubles)
AstuceUne seule ligne, pas trois

Une compréhension doit rester lisible. Dès qu’elle comporte deux for imbriqués et un if, la boucle explicite du chapitre suivant est préférable.

Le critère est simple : si vous devez la relire deux fois pour la comprendre, écrivez une boucle.

3.7 Calculer sur une liste

v = [4, 8, 15, 16, 23, 42] print(sum(v)) # somme print(len(v)) # nombre d'elements print(sum(v) / len(v)) # moyenne : pas de fonction native ! print(max(v), min(v)) print(sorted(v)[len(v)//2]) # une mediane approximative
NotePourquoi pas de mean ?

Python de base n’a pas de fonction moyenne : ce n’est pas un langage statistique. Le module statistics en fournit une, mais lente.

import statistics
print(statistics.mean(v))
print(statistics.stdev(v))

Pour de vrais calculs, on emploie NumPy — d’où le plan de ce cours, qui aborde le langage d’abord et les bibliothèques ensuite.

3.8 Le tuple, une liste qu’on ne peut pas modifier

point = (45.2, 8.4) print(point[0]) # point[0] = 50 # TypeError : un tuple est immuable x, y = point # affectation multiple print(x, y)

Le tuple sert quand un groupe de valeurs forme un tout qui ne doit pas changer — des coordonnées, un couple clé-valeur. Il permet aussi l’affectation multiple, très employée pour récupérer les résultats multiples d’une fonction.

À vous

À partir d’une liste de notes, calculez la moyenne, la liste des notes supérieures ou égales à 10, et le nombre de reçus — sans écrire de boucle explicite.

notes = [12, 8, 15, 6, 17, 11, 14, 9] # a completer
moyenne = sum(notes) / len(notes)
print(f"moyenne : {moyenne:.2f}")

recues = [n for n in notes if n >= 10]
print(recues)
print(f"{len(recues)} recus sur {len(notes)}")
print(f"taux : {len(recues) / len(notes):.1%}")

Les compréhensions remplacent ici trois boucles. Comparez avec ce que R écrirait — notes[notes >= 10] — et vous mesurerez ce que la vectorisation apporte, et ce que NumPy viendra rétablir.

Ce qu’il faut retenir

Écriture Effet
[4, 8, 15] créer une liste
v[0], v[-1] premier, dernier — on compte depuis 0
v[1:4] tranche — borne de fin exclue
v[0] = 100 modifier — une liste est modifiable, une chaîne non
append, insert, extend, remove, pop ajouter et retirer
méthodes de liste modifient sur place et renvoient None
sorted(v) / v.sort() renvoie une copie triée / trie sur place
b = a.copy() vraie copie — b = a n’en est pas une
[x * 2 for x in v] compréhension : construire une liste en une ligne
[x for x in v if x > 15] compréhension filtrante
sum, len, max, min calculer — pas de mean natif
(45.2, 8.4) tuple : une liste immuable

Le chapitre suivant formalise ce que les compréhensions faisaient discrètement : la boucle, qui répète un traitement.