Les listes
Une vidéo de présentation d’un premier container, les listes…
Nous avons vu jusqu’à présent deux types d’objets, que l’on peut considérer « atomiques » :
Lorsque l’on veut manipuler un ensemble de ces objets, et même un ensemble d’objets plus complexes, Python dispose de plusieurs containers pour stocker ces objets.
Le plus simple de ces containers est la list
qui est une collection ordonnée d’objets (éventuellement hétérogènes) définie avec les opérateurs [
et ]
:
>>> l = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
>>> l
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
>>> len(l)
10
Tout est objet en Python et la list
n’échappe bien sûr pas à la règle. En tant qu’objet, la list
dispose donc de plusieurs méthodes permettant d’effectuer un certain nombre d’opérations courantes sur ce type de structure. Les plus élémentaires sont abordées ci dessous.
Slicing
Comme les chaînes de caractères, une list
est une sequence, et à ce titre dispose des mêmes opérations d’indexation et de slicing. Toutes les propriétés décrites dans le paragraphe sur le Slicing sont valables:
>>> l[0] # accès au premier élément de la liste
0
>>> l[-1] # accès au dernier élément de la liste
34
>>> l[::2] # les éléments de rang pair
[0, 1, 3, 8, 21]
A expérimenter…
Mettre en oeuvre quelques opérations de slicing un peu plus complexes que celles qui précèdent, conjecturer le résultat et vérifier dans un interpréteur interactif.
Modification des éléments d’une liste
Contrairement aux chaînes de caractères, la list
est une séquence mutable. Il est donc possible de modifier ses éléments:
>>> l = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
>>> l[0] = 'zéro'
>>> l
['zéro', 1, 1, 2, 3, 5, 8, 13, 21, 34] # liste hétérogène
Modification de la structure d’une liste
On peut également modifier la structure de la liste, en ajoutant ou en retirant des éléments, ce qui va modifier sa longueur.
On peut ajouter des éléments n’importe où dans une list
en utilisant la méthode insert()
:
>>> l.insert(2,99)
>>> l
['zéro', 1, 99, 1, 2, 3, 5, 8, 13, 21, 34]
ou les supprimer avec remove()
:
>>> l.remove(99) # remove(elt) ne retourne rien
>>> l
['zéro', 1, 1, 2, 3, 5, 8, 13, 21, 34]
La méthode pop()
permet également de retirer un élément de la liste en passant son index en argument. L’élément retiré est disponible et peut être affecté à une nouvelle variable (référence):
>>> first = l.pop(0) # pop(index) retourne l'élément retiré de la liste
>>> first
'zéro'
>>> l
[1, 1, 2, 3, 5, 8, 13, 21, 34]
L’ajout d’un élément à la fin d’une list
est un cas très courant qui dispose d’une méthode particulière append()
. Cette méthode est plus rapide que insert()
car la liste réserve automatiquement un espace libre à la fin. A contrario, insert()
est plus lente car les opérations qu’elle met en oeuvre sont plus complexes:
>>> l.append(55)
>>> l
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
Il existe deux façons d’ajouter des éléments à une liste :
ajouter chaque élément individuellement avec
append()
par exemple ;ou ajouter tous les éléments d’une autre liste avec avec
extend()
.
La deuxième méthode s’appelle la concaténation:
>>> l.extend([89, 144])
>>> l
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]
En Python, la redéfinition d’opérateur est possible comme on l’a vu avec les chaines de caractères. Pour les listes, l’opérateur +
a été redéfini pour se comporter (presque) comme extend()
:
>>> l = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
>>> l = l + [89, 144]
>>> l
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144]
A expérimenter…
Ci dessus il est dit que l’opérateur +
a été redéfini pour se comporter (presque) comme extend()
. Observer attentivement ce que retournent les opérations ci dessous, effectuées après la création de la liste:
>>> l.extend([89, 144])
et:
>>> l + [89, 144]
Conclusion ?
Utilisation des slices
L’utilisation des slices est une méthode puissante de modification de liste.
La syntaxe s[i:j] = t
où s
et t
sont des séquences insère la séquence t
dans la portion de liste définie par s[i:j]
. La taille des deux séquences peut différer.
On peut s’en servir pour l’insertion:
>>> l = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
>>> l[2:2] = [0, 0, 0]
>>> l
[0, 1, 0, 0, 0, 1, 2, 3, 5, 8, 13, 21, 34]
la suppression:
>>> l[2:5] = []
>>> l
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
la modification:
>>> l[6:] = [6, 7, 8, 9]
>>> l
[0, 1, 1, 2, 3, 5, 6, 7, 8, 9]
Appartenance
Comme chaque sequence, la chaine de caractères et celles que l’on va aborder par la suite, la list
dispose d’un mécanisme simple pour tester l’appartenance ou non d’un élément avec l’opérateur in
:
>>> 34 in l
True
>>> 63 in l
False
Itérer sur une liste
L’opérateur in
est également utilisé pour l’itération:
>>> l = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
>>> for elt in l:
... print(elt)
...
1
1
2
3
5
8
13
21
34
Il est important de comprendre qu’en Python on itère par défaut sur les éléments de la liste, pas sur les index.
❌ Le code ci dessous produit le même résultat que le code ci dessus, mais est moins concis, donc susceptible de produire plus d’erreurs et plus difficile à comprendre et à maintenir. Il est donc à éviter:
>>> for i in range(len(l)):
... print(l[i])
...
0
1
1
2
3
5
8
13
21
34
Si l’index est requis, la fonction enumerate()
est utilisée:
>>> for i, elt in enumerate(l):
... print(i, elt)
...
0 0
1 1
2 1
3 2
4 3
5 5
6 8
7 13
8 21
9 34
Autres méthodes
Python dispose de nombreuses méthodes pour manipuler les séquences dont les listes font partie.
On peut par exemple effectuer une recherche avec index()
:
>>> l.index(89)
10
ou inverser la list
avec reverse()
:
>>> l.reverse()
>>> l
[144, 89, 55, 34, 21, 13, 8, 5, 3, 2, 1, 1]
Listes numériques
Dans le cas particulier où la liste manipulée est uniquement composée de nombres, Python dispose en standard de fonctions permettant de rechercher les valeurs min()
et max()
:
>>> min(l)
1
>>> max(l)
144
Ces fonctions sont dites vectorisées car l’argument est un vecteur de données. Le parcours des données, pour en rechercher le minimum ou le maximum, se fait de façon optimisée dans les fonctions min()
et max()
.
Il est dans la très grande majorité des cas inutile de réécrire sa propre fonction pour réaliser les opérations courantes. Python dispose de très nombreuses fonctions mathématiques, en standard ou dans un module (qu’il convient alors d’importer). Un exemple ci dessous avec les fonctions mean()
et median()
du module statistics
:
>>> from statistics import mean, median
>>> mean(l)
31.333333333333332
>>> median(l)
10.5
Le tri élémentaire
Parmi toutes les méthodes de liste, la méthode sort()
permet de trier la liste sur place:
>>> l
[7, 6, 9, 2, 4, 5, 8, 3, 1, 0]
>>> l.sort()
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Le paramètre reverse permet de changer l’ordre de tri:
>>> l.sort(reverse=True)
>>> l
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
L’algorithme utilisé est le Timsort, dérivé du merge sort et insertion sort.
Listes de listes
Une list
peut contenir n’importe quel objet Python et en particulier d’autres listes:
>>> even = [0, 2, 4, 6, 8]
>>> odd = [1, 3, 5, 7, 9]
>>> fib = [1, 1, 2, 3, 5]
>>> primes = [2, 3, 5, 7, 11]
>>> nums = [even, odd, fib, primes]
>>> type(nums)
<class 'list'>
>>> nums
[[0, 2, 4, 6, 8], [1, 3, 5, 7, 9], [1, 1, 2, 3, 5], [2, 3, 5, 7, 11]]
Les éléments de la liste nums
sont eux même des listes. Et donc, nums[1]
est aussi une list
>>> type(nums[1])
<class 'list'>
>>> nums[1]
[1, 3, 5, 7, 9]
nums[1]
est une liste d’entiers, ce qu’on constate en observant le type de son quatrième élément:
>>> type(nums[1][3])
<class 'int'>
En effet, puisque nums[1]
est une liste, on accède à ses éléments avec le même opérateur d’indexation:
>>> nums[1][3]
7
Les list comprehension
On peut construire une list
à partir d’une boucle for
et de la méthode append()
:
>>> cubes = []
>>> for i in range(10):
...: cubes.append(i**3)
...:
>>> cubes
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
Cependant Python possède une syntaxe plus concise et plus élégante, les list comprehension
:
>>> cubes = [ i**3 for i in range(10)]
>>> cubes
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
Note
La variable utilisée à l’intérieur de la list comprehension
est locale à celle ci:
>>> i = 99
>>> [ i**3 for i in range(10)]
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
>>> i
99
Il est possible d’ajouter un prédicat à une list comprehension
:
>>> [ i**3 for i in range(10) if i%2 == 0]
[0, 8, 64, 216, 512]
ou d’imbriquer des boucles, qui sont exécutées dans l’ordre d’apparition dans la list comprehension
:
>>> [i+str(j) for i in 'ab' for j in range(3)]
['a0', 'a1', 'a2', 'b0', 'b1', 'b2']
ou bien encore de construire des listes de listes:
>>> [[i+str(j) for i in 'abcdefgh'] for j in range(1, 9)]
[['a1', 'b1', 'c1', 'd1', 'e1', 'f1', 'g1', 'h1'],
['a2', 'b2', 'c2', 'd2', 'e2', 'f2', 'g2', 'h2'],
['a3', 'b3', 'c3', 'd3', 'e3', 'f3', 'g3', 'h3'],
['a4', 'b4', 'c4', 'd4', 'e4', 'f4', 'g4', 'h4'],
['a5', 'b5', 'c5', 'd5', 'e5', 'f5', 'g5', 'h5'],
['a6', 'b6', 'c6', 'd6', 'e6', 'f6', 'g6', 'h6'],
['a7', 'b7', 'c7', 'd7', 'e7', 'f7', 'g7', 'h7'],
['a8', 'b8', 'c8', 'd8', 'e8', 'f8', 'g8', 'h8']]
On peut également utiliser une clause else
dans le prédicat. Dans ce cas, le test est placé avant for
:
>>> marks = [8, 12, 7, 15, 16, 13, 4, 14, 9, 15]
>>> [ 'pass' if x >=10 else 'failed' for x in marks]
['failed', 'pass', 'failed', 'pass', 'pass', 'pass', 'failed', 'pass', 'failed', 'pass']
Chaque fois que ce sera possible cette syntaxe devra être utilisée.
Copies et références
Lorsqu’on affecte un objet à un nom de variable (une référence), Python crée une relation entre les deux. On dit que la référence « pointe » vers l’objet:
>>> l1 = [0, 1, 2, 3, 4]
Ici la référence l1
pointe vers la liste [0, 1, 2, 3, 4]
. L’instruction l2 = l1
duplique la référence vers la liste [0, 1, 2, 3, 4]
, mais pas la liste elle même ! Pour s’en convaincre:
>>> id(l1)
39850888
>>> l2 = l1
>>> id(l2)
39850888
l1
et l2
pointent bien vers le même objet. Ce comportement peut avoir des conséquences inattendues, s’il n’est pas bien compris. En particulier modifier l1
a pour conséquence de modifier l2
:
>>> l1.append(5)
>>> l1
[0, 1, 2, 3, 4, 5]
>>> l2
[0, 1, 2, 3, 4, 5]
Pour dupliquer, la liste, plutôt que la référence, il faut forcer la création d’un nouvel objet:
>>> l3 = l1[:] # ou l3 = list(l1)
>>> l3
[0, 1, 2, 3, 4, 5]
>>> l1.append(6)
>>> l1
[0, 1, 2, 3, 4, 5, 6]
>>> l3
[0, 1, 2, 3, 4, 5]
Lorsque la liste est une structure complexe (contenant d’autres objets), il est plus sûr de faire appel à deepcopy()
:
>>> from copy import deepcopy
>>> a = [[1,2],[3],[4]]
>>> id(a)
1471379026944
>>> b = deepcopy(a)
>>> id(b)
1471379028736
Ce qu’il faut retenir
Une liste se délimite avec
[
et]
Une liste se délimite avec
(
et)
Une liste se délimite avec
{
et}
Une liste peut contenir des objets hétérogènes
Une liste est une collection ordonnée
Les opérations de slicing sur une liste sont légèrement différentes des opérations de slicing sur une chaine de caractères
Une liste est mutable
Une fois créée, la taille d’une liste est figée
est une méthode permettant d’insérer un élément n’importe où dans la liste
est une méthode permettant d’insérer un élément à la fin de la liste
est une méthode permettant de supprimer un élément de la liste sans le retourner
est une méthode permettant de supprimer un élément de la liste en le retournant
append
est une méthode qui permet d’ajouter plusieurs éléments à la liste en une seule opérationextend
est une méthode qui permet d’ajouter un seul élément à la liste en une seule opérationLe slicing peut être utilisé pour extraire des éléments d’une liste
Le slicing peut être utilisé pour insérer des éléments dans une liste
Le slicing ne peut pas modifier la taille d’une liste
Sur une liste, l’opérateur d’appartenance
in
ne fonctionne pas tout à fait comme pour une chaine de caractèresLa façon privilégiée de parcourir une liste est d’itérer sur ses éléments
La façon privilégiée de parcourir une liste est d’itérer sur l’index de ses éléments
Une liste dispose d’une méthode pour trier ses éléments
Une liste peut contenir n’importe quel objet Python
On peut construire des listes imbriquées
Une est une façon concise de construire une liste
Une list comprehension ne contient pas de structure conditionnelle
Une list comprehension peut construire une liste unidimensionnelle
Une list comprehension doit construire une liste unidimensionnelle