.. _tut-listes: ********** Les listes ********** Une vidéo de présentation d'un premier container, les listes... .. raw:: html Nous avons vu jusqu'à présent deux types d'objets, que l'on peut considérer "atomiques" : - les numériques : :class:`int`, :class:`float` et :class:`complex`, ; - et les chaînes de caractères : :class:`str`. 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 :class:`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 :class:`list` n'échappe bien sûr pas à la règle. En tant qu'objet, la :class:`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 :class:`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 :ref:`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] .. admonition:: 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 :class:`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 :class:`list` en utilisant la méthode :meth:`~list.insert`:: >>> l.insert(2,99) >>> l ['zéro', 1, 99, 1, 2, 3, 5, 8, 13, 21, 34] ou les supprimer avec :meth:`~list.remove`:: >>> l.remove(99) # remove(elt) ne retourne rien >>> l ['zéro', 1, 1, 2, 3, 5, 8, 13, 21, 34] La méthode :meth:`~list.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 :class:`list` est un cas très courant qui dispose d'une méthode particulière :meth:`~list.append`. Cette méthode est plus rapide que :meth:`~list.insert` car la liste réserve automatiquement un espace libre à la fin. A contrario, :meth:`~list.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 :meth:`~list.append` par exemple ; - ou ajouter tous les éléments d'une autre liste avec avec :meth:`~list.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 :meth:`~list.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] .. admonition:: A expérimenter... Ci dessus il est dit que l'opérateur ``+`` a été redéfini pour se comporter (presque) comme :meth:`~list.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 :class:`list` dispose d'un mécanisme simple pour tester l'appartenance ou non d'un élément avec l'opérateur :keyword:`in`:: >>> 34 in l True >>> 63 in l False Itérer sur une liste ==================== L'opérateur :keyword:`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 :func:`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 :meth:`~list.index`:: >>> l.index(89) 10 ou inverser la :class:`list` avec :meth:`~list.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 :func:`min` et :func:`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 :func:`min` et :func:`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 :func:`~statistics.mean` et :func:`~statistics.median` du module :mod:`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 :meth:`~list.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 :class:`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) >>> 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 :class:`list` :: >>> type(nums[1]) >>> 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]) 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 :class:`list` à partir d'une boucle :keyword:`for` et de la méthode :meth:`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 :keyword:`else` dans le prédicat. Dans ce cas, le test est placé avant :keyword:`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 à :func:`~copy.deepcopy`:: >>> from copy import deepcopy >>> a = [[1,2],[3],[4]] >>> id(a) 1471379026944 >>> b = deepcopy(a) >>> id(b) 1471379028736 Ce qu'il faut retenir ===================== .. quiz:: quizz-06 :title: Les listes - :quiz:`{"type":"TF","answer":"T"}` Une liste se délimite avec ``[`` et ``]`` - :quiz:`{"type":"TF","answer":"F"}` Une liste se délimite avec ``(`` et ``)`` - :quiz:`{"type":"TF","answer":"F"}` Une liste se délimite avec ``{`` et ``}`` - :quiz:`{"type":"TF","answer":"T"}` Une liste peut contenir des objets hétérogènes - :quiz:`{"type":"TF","answer":"T"}` Une liste est une collection ordonnée - :quiz:`{"type":"TF","answer":"F"}` 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 - :quiz:`{"type":"TF","answer":"T"}` Une liste est *mutable* - :quiz:`{"type":"TF","answer":"F"}` Une fois créée, la taille d'une liste est figée - :quiz:`{"type":"FB","answer":"insert", "size":5}` est une méthode permettant d'insérer un élément n'importe où dans la liste - :quiz:`{"type":"FB","answer":"append", "size":5}` est une méthode permettant d'insérer un élément à la fin de la liste - :quiz:`{"type":"FB","answer":"remove", "size":5}` est une méthode permettant de supprimer un élément de la liste sans le retourner - :quiz:`{"type":"FB","answer":"pop", "size":5}` est une méthode permettant de supprimer un élément de la liste en le retournant - :quiz:`{"type":"TF","answer":"F"}` ``append`` est une méthode qui permet d'ajouter plusieurs éléments à la liste en une seule opération - :quiz:`{"type":"TF","answer":"F"}` ``extend`` est une méthode qui permet d'ajouter un seul élément à la liste en une seule opération - :quiz:`{"type":"TF","answer":"T"}` Le slicing peut être utilisé pour extraire des éléments d'une liste - :quiz:`{"type":"TF","answer":"T"}` Le slicing peut être utilisé pour insérer des éléments dans une liste - :quiz:`{"type":"TF","answer":"F"}` Le slicing ne peut pas modifier la taille d'une liste - :quiz:`{"type":"TF","answer":"T"}` Sur une liste, l'opérateur d'appartenance ``in`` ne fonctionne pas tout à fait comme pour une chaine de caractères - :quiz:`{"type":"TF","answer":"T"}` La façon privilégiée de parcourir une liste est d'itérer sur ses éléments - :quiz:`{"type":"TF","answer":"F"}` La façon privilégiée de parcourir une liste est d'itérer sur l'index de ses éléments - :quiz:`{"type":"TF","answer":"T"}` Une liste dispose d'une méthode pour trier ses éléments - :quiz:`{"type":"TF","answer":"T"}` Une liste peut contenir n'importe quel objet Python - :quiz:`{"type":"TF","answer":"T"}` On peut construire des listes imbriquées - Une :quiz:`{"type":"FB","answer":"list comprehension", "size":18}` est une façon concise de construire une liste - :quiz:`{"type":"TF","answer":"F"}` Une *list comprehension* ne contient pas de structure conditionnelle - :quiz:`{"type":"TF","answer":"T"}` Une *list comprehension* peut construire une liste unidimensionnelle - :quiz:`{"type":"TF","answer":"F"}` Une *list comprehension* doit construire une liste unidimensionnelle