Les list
en python ressemblent au premier abord fortement aux tuple
. La différence essentielle est qu’il s’agit d’une structure dynamique: on peut remplacer, enlever et rajouter des éléments.
La syntaxe de création de list
par rapport aux tuple
remplace les parenthèses par des crochets:
>>> mon_tuple = (1, 2, 3)
>>> type(mon_tuple)
<class 'tuple'>
>>> ma_liste = [1, 2, 3]
>>> type(ma_liste)
<class 'list'>
Comme pour les tuple
on peut récupérer individuellement les éléments en utilisant:
les indices
>>> nombres = [6, 3, 7, 2, 5]
>>> nombres[0]
6
>>> nombres[4]
5
Mentionnons au passage une possibilité très pratique (et valable aussi pour les tuple
) d’utiliser des indices négatifs pour parcourir une list
en partant de la fin:
>>> nombres = [1, 5, 2, 4, 3]
>>> nombres[-1]
3
>>> nombres[-2]
4
>>> nombres[-4]
5
la déstructuration
>>> nombres = [1, 2, 3, 4]
>>> a, b, c, d = nombres
>>> a
1
>>> b
2
>>> c
3
>>> d
4
>>> premier, *reste, dernier = nombres
>>> premier
1
>>> reste
[2, 3]
>>> dernier
4
On va introduire les premières fonctionnalités les plus utiles pour les listes. On verra le reste dans une autre leçon.
Pour rajouter un élément à la fin d’une liste on utilise la méthode append
>>> ma_liste = list()
>>> ma_liste
[]
>>> ma_liste.append(5)
>>> ma_liste
[5]
>>> ma_liste.append(2)
>>> ma_liste
[5, 2]
>>> ma_liste.append(10)
>>> ma_liste
[5, 2, 10]
REMARQUE la majorité des types python arrivent avec leurs fonctionnalités sous formes de différentes méthodes. On peut les lister en utilisant la fonction dir
. Par exemple pour les list
>>> ma_liste = [1, 2, 3]
>>> dir(ma_liste)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Notez que les méthodes dont le nom commence et finit par __
sont appelées SPECIALMETHODS
(ou dunder méthodes), elles permettent de prescrire le comportement des objets par rapport aux primitives du langage python. On y reviendra beaucoup plus tard lorsqu’on parlera des métaprotocoles.
Parmi les méthodes suivantes on peut remarquer append
que l’on vient de voir. L’autre méthode que l’on présentera (partiellement) aujourd’hui est pop
. Elle permet d’extraire le dernier élément de la liste:
>>> nombres
[1, 2, 3]
>>> c = nombres.pop()
>>> c
3
>>> nombres
[1, 2]
>>> b = nombres.pop()
>>> b
2
>>> a = nombres.pop()
>>> a
1
>>> nombres
[]
>>> nombres.pop()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: pop from empty list
ATTENTION comme on le voit ci-dessus, appeler pop
sur une liste vide fait planter python!
Finalement mentionnons que l’on peut remplacer un élément via son indice de position via la syntaxe suivante:
>>> nombres = [0, 1, 2, 3]
>>> nombres
[0, 1, 2, 3]
>>> nombres[1] = 243
>>> nombres
[0, 243, 2, 3]
>>> nombres[3] = "abcde"
>>> nombres
[0, 243, 2, 'abcde']
>>> nombres[0] = (1, 2, 3)
>>> nombres
[(1, 2, 3), 243, 2, 'abcde']
REMARQUE on constate au passage que comme pour les tuple
la structure list
est hétérogène. Elle peut contenir des éléments de types différents. Ceci dit dans la majorité des cas les list
utilisées sont homogènes, et l’utilisation de list
hétérogènes compliquera la compréhension du programme.
On va dans cette section revenir sur un phénomène que l’on a décrit mais pas encore observé. On a déjà mentionné le fait qu’il faut faire la distinction entre une variable et l’objet vers lequel elle pointe. Le bout de code suivant démontre le comportement que cela implique:
>>> nombres = [1, 2, 3]
>>> copie = nombres
>>> nombres
[1, 2, 3]
>>> copie
[1, 2, 3]
>>> copie.append(4)
>>> copie
[1, 2, 3, 4]
>>> nombres
[1, 2, 3, 4]
On constate que nombres
et copie
font en fait référence au même objet. (contrairement à ce que le nom de variable pourrait laisser supposer) Lorsqu’on modifie copie
en utilisant la méthode append
, le résultat s’est aussi répercuté sur nombres
.
Pour un code assez réduit, cela ne pose par forcément de problème, par contre à une centaine de lignes de code de distance, voire dans des fichiers distincts, c’est une source de bogue redoutable.
On peut utiliser l’opérateur is
et la fonction id
pour détecter le fait que deux variables pointent vers le même objet:
>>> nombres = [1, 2, 3]
>>> copie = nombres
>>> copie is nombres
True
>>> id(copie)
140696165457024
>>> id(nombres)
140696165457024
Mais le phénomène est encore plus redoutable que l’on pourrait le craindre
>>> ma_liste = [[1, 2], 3, 4]
>>> autre_liste = [1, 2, 3]
>>> ma_liste is autre_liste
False
>>> autre_liste[0] = ma_liste[0]
>>> ma_liste is autre_liste
False
>>> ma_liste
[[1, 2], 3, 4]
>>> autre_liste
[[1, 2], 2, 3]
>>> autre_liste[0].append(3)
>>> autre_liste
[[1, 2, 3], 2, 3]
>>> ma_liste
[[1, 2, 3], 3, 4]
>>> autre_liste is ma_liste
False
>>> autre_liste[0] is ma_liste[0]
True
On voit ici que les deux listes sont distinctes mais leur premier élément identique. Les éléments des list
doivent en fait être vus comme des variables implicites qui pointent vers des objets python.
De manière générale, on fera donc preuve de discipline lorsqu’on manipulera de tels objets. Si on souhaite faire une copie complète d’un objet on pourra utiliser la recette suivante (qu’on ne détaillera pas encore car les modules seront vus plus tard)
>>> import copy
>>> ma_liste = [1, 2, 3]
>>> autre_liste = copy.deepcopy(ma_liste)
>>> ma_liste
[1, 2, 3]
>>> autre_liste
[1, 2, 3]
>>> autre_liste.append(4)
>>> ma_liste
[1, 2, 3]
>>> autre_liste
[1, 2, 3, 4]
Pour le type list
on pourra essayer
>>> help(SEQUENCES)
Pour des détails sur les mutations on regardera
>>> help(LISTS)
Pour la création de listes explicites on consultera
>>> help(LISTLITERALS)
A ce stade du cours on peut produire le code suivant (mais on verra la bonne manière de s’y prendre dans la prochaine leçon)
>>> resultat = list()
>>> nombre = 1
>>> while nombre <= 100:
... resultat.append(nombre)
... nombre = nombre + 1
...
>>> resultat
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
On peut s’inspirer du code précédent pour faire:
>>> resultat = list()
>>> nombre = 0
>>> while nombre <= 1000:
... est_divisible_par_2 = nombre % 2 == 0
... est_divisible_par_3 = nombre % 3 == 0
... if est_divisible_par_2 and not est_divisible_par_3:
... resultat.append(nombre)
... if est_divisible_par_3 and not est_divisible_par_2:
... resultat.append(nombre)
... nombre = nombre + 1
...
>>> len(resultat)
501
>>> resultat[29]
58
On peut produire le code suivant.
>>> n = 7
>>> resultat = [n]
>>> while n != 1:
... if n % 2 == 0:
... n = n // 2
... else:
... n = 3 * n + 1
... resultat.append(n)
...
>>> print(resultat)
[7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1]