Listes: Acte I

Création

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'>

Accès aux éléments

Comme pour les tuple on peut récupérer individuellement les éléments en utilisant:

Modifications

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.

Mutation

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]

Exercices

  1. Donner un code construisant la liste des entiers de 1 à 100.
  2. Construisez la liste des nombres compris entre 0 et 1000 divisibles par 2 ou 3 mais pas les deux. Combien y en a t il? Quel est le 30ième?
  3. Reprenez l’exercice de la feuille précédente sur la suite de Syracuse, mais au lieu d’afficher les nombres, renvoyer la liste de ces nombres.

Pour aller plus loin

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)

Corrections

  1. 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]
  2. 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 voit qu’il y 501 nombres satisfaisant la condition et que le 30ième est 58.
  3. 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]