.. _structures-donnees-python:
*******************************
Structures de données en Python
*******************************
Dans le chapitre :ref:`structures-donnees`, on a abordé les principales structures de données que l'on rencontre en programmation. Quelques exemples ont été générés avec l'aide de l'intelligence artificielle dans les langages C et Python. Dans ce chapitre, nous allons aborder plus en détail les spécificités du langage Python.
On a vu dans le chapitre :ref:`structures-donnees-c` que le langage C est un langage de bas niveau qui impose de gérer soi-même la mémoire et les structures de données. Le langage Python, quant à lui, est un langage de haut niveau, qui offre des structures de données intégrées et une gestion automatique de la mémoire.
Nous allons donc nous concentrer sur les structures de données intégrées nativement au langage Python, telles que les listes, les tuples, les ensembles et les dictionnaires. Nous verrons comment les utiliser efficacement pour résoudre des problèmes courants en programmation.
.. note::
Python est livré avec une bibliothèque standard riche qui fournit de nombreuses fonctionnalités pour travailler avec des structures de données plus complexes. Pour garder une empreinte mémoire raisonnable, ces structures de données complexes ne sont pas chargées em mémoire par défaut. Il est donc nécessaire d'importer les modules correspondants pour les utiliser. On utilise pour cela le mot-clé ``import``.
.. _python-strings:
Les chaines de caractères
=========================
Une vidéo de présentation des chaînes de caractères...
.. raw:: html
Généralités
-----------
En Python tout est objet, et donc les chaînes de caractères sont des objets qui peuvent se définir à partir des simples ``'`` ou des double ``"`` quotes.
.. code-block:: python
>>> a = "hello"
>>> b = 'hello'
>>> c = "world"
Ici on a créé 3 objets "chaîne de caractères" :``a``, ``b`` et ``c``.
.. note::
En programmation objet, la création d'un objet fait appel à un constructeur auquel on passe le contenu de l'objet à créer. Ainsi pour créer la chaîne de caractère "hello", on peut faire appel au constructeur de la classe :class:`str`.
.. code-block:: python
>>> a = str('hello')
>>> a
'hello'
Cependant les caractères ``"`` ou ``'`` produisent le même résultat avec une syntaxe plus légère. On appelle ça du sucre syntaxique.
Chaque objet créé possède plusieurs caractéristiques parmi lesquelles :
- son contenu ;
- son identité, obtenue avec la fonction :func:`id`.
On peut tester l'égalité du contenu de deux objets avec l'opérateur ``==``.
.. code-block:: python
>>> a == b
True
>>> a == c
False
Ici, en plus d'avoir le même contenu, les variables ``a`` et ``b`` ont la même identité, c'est à dire sont deux références distinctes vers le même objet. On peut le vérifier avec la fonction :func:`id`. On peut également vérifier que a et b sont deux références distinctes vers le même objet avec l'opérateur ``is``.
.. code-block:: python
>>> id(a)
37940000
>>> id(b)
37940000
>>> a is b
True
.. Note::
Dans ce cas, lors de la création de ``b`` Python a trouvé un objet identique en mémoire et n'a pas éprouvé le besoin d'en créer un second. Il a simplement créé une nouvelle référence vers l'objet déjà référencé par ``a``.
Une curiosité, lorsque la chaîne de caractère est plus longue, deux objets différents sont créés.
.. code-block:: python
>>> a = "hello"*1000 # "hellohellohello...hello" 1000 fois
>>> b = "hello"*1000
>>> a is b
False
En généralisant l'exemple ci dessus, on peut avoir deux références ``o1`` et ``o2`` vers deux objets distincts contenant une même valeur. Dans ce cas :
.. code-block:: python
>>> o1 == o2
True
>>> o1 is o2
False
On peut utiliser les délimiteurs dans la chaîne de caractère elle même, mais il faut les échapper avec ``\``.
.. code-block:: python
>>> s = "J'entends et j'oublie, je vois et je me souviens, je fais et je comprends"
>>> s
"J'entends et j'oublie, je vois et je me souviens, je fais et je comprends"
>>> s = 'J\'entends et j\'oublie, je vois et je me souviens, je fais et je comprends'
>>> s
"J'entends et j'oublie, je vois et je me souviens, je fais et je comprends"
On peut insérer des caractères spéciaux utilisés pour l'affichage: la tabulation ``\t`` et le retour à la ligne ``\n``. Ces caractères apparaissent tels quels dans la chaîne de caractères mais sont interprétés correctement à l'affichage.
.. code-block:: python
>>> s = "Veni,\t vidi,\t vici\n"
>>> s
'Veni,\t vidi,\t vici\n'
>>> print(s)
Veni, vidi, vici
>>>
.. note::
Il faut différencier la chaîne de caractère elle même, de sa représentation. Les caractères imprimables (lettres, chiffres, la plupart des caractères spéciaux) sont rendus tels quels, alors que les caractères non imprimables sont transformés par la fonction :func:`print` avant affichage. Par exemple ``\n`` est transformé en saut de ligne.
Les chaînes multilignes, que l'on a déjà aperçues dans les ``docstring``, utilisent un triple séparateur.
.. code-block:: python
>>> s = """
... Si tu peux voir détruit l'ouvrage de ta vie
... Et sans dire un seul mot te mettre à rebâtir
... Ou perdre en un seul coup le gain de cent parties
... Sans un geste et sans un soupir
... """
>>> s
"Si tu peux voir détruit l'ouvrage de ta vie\nEt sans dire un seul mot te mettre
à rebâtir\nOu perdre en un seul coup le gain de cent parties\nSans un geste et
sans un soupir\n"
>>> print(s)
Si tu peux voir détruit l'ouvrage de ta vie
Et sans dire un seul mot te mettre à rebâtir
Ou perdre en un seul coup le gain de cent parties
Sans un geste et sans un soupir
Python est un langage permettant la redéfinition des opérateurs, ce qui permet de les adapter au contexte de l'objet. Pour les chaînes de caractères, l'opérateur ``+`` a été redéfini comme opérateur de concaténation, et comme on l'a aperçu précédemment, l'opérateur ``*`` a également été redéfini.
.. code-block:: python
>>> 'Hello ' + 'world ' + '!'
'Hello world !'
>>> 'Hi '*3
'Hi Hi Hi '
Les chaînes de caractères sont des séquences
--------------------------------------------
Les séquences jouent un grand rôle dans Python. Une séquence est une collection ordonnées d'objets. Pour le cas particulier des chaînes de caractères, il s'agit évidemment d'une collection ordonnée de caractères.
On peut connaitre la longueur d'une séquence avec la fonction :func:`len`. Le premier caractère de la chaîne est à l'index ``0``, le dernier à l'index ``len(s)-1``.
>>> s = "Hello World!"
>>> len(s) # longueur de la chaine
13
>>> s[0] # premier élément de la chaine
'H'
>>> s[len(s)-1] # dernier élément de la chaine
'!'
>>> s[8]
'r'
Python utilise les index négatifs pour utiliser la fin de chaine comme référence, plutôt que le début.
.. code-block:: python
>>> s[-1] # dernier élément de la liste
'!'
>>> s[-3]
'l'
Une façon simple de visualiser ce fonctionnement est de considérer que les index définissent les intervalles plutôt que les éléments eux mêmes.
.. image:: images/python-05-chaines-fig-01.png
:scale: 75 %
:align: center
Lorsque l'index est positif, on prend l'élément à droite et lorsque l'index est négatif, on prend l'élément à gauche. Si on essaye d'accéder à un élément qui n'existe pas, une erreur se produit.
.. code-block:: python
>>> s[8]
'r'
>>> s[-6]
'o'
>>> s[14]
Traceback (most recent call last):
File "", line 1, in
IndexError: string index out of range
Comme exposé précédemment, la dernière ligne décrit la nature de l'erreur en langage naturel et donne une piste pour la résolution du problème.
.. _python-strings-slicing:
Slicing
.......
On peut découper la chaîne de caractère avec la syntaxe ``s[d:f:p]`` où :
- ``d`` représente l'index de début ;
- ``f`` l'index de fin ;
- et ``p`` le pas.
Dans cette syntaxe, le premier index ``d`` est inclus, le dernier ``f`` ne l'est pas. ``s[d:f]`` produit donc la chaîne de caractère composée des éléments ``s[d]`` à ``s[f-1]``. On prendra donc tous les éléments à droite de ``d`` et à gauche de ``f``.
.. code-block:: python
>>> s[0:3]
'Hel'
Il y a deux avantages majeurs à cette syntaxe, illustrés par les deux propriétés suivantes :
- ``s[:i] + s[i:] = s``
- ``f-d`` est la longueur de la chaîne de caractère
La première rend la chaine simple à découper, à l'image de `la relation de Chasles `_ pour les vecteurs.
La seconde fournit une relation simple entre la longueur de la chaine et les index de début et de fin.
.. note::
Dijkstra (l'inventeur de `l'algorithme utilisé dans les GPS `_) donne une explication détaillée de la pertinence de cette convention dans `cet article `_.
Lorsque l'un des index est omis, il est remplacé par une valeur par défaut, dépendante du contexte (sens du parcours). Le sens du parcours est défini par les index de début ``d`` et de fin ``f``.
Pour un parcours de gauche à droite (Left To Right : LTR), pour lequel ``d ≤ f`` :
- la valeur par défaut de ``d`` est ``0`` ;
- la valeur par défaut de ``f`` est ``len(s)-1`` ;
- la valeur par défaut de ``p`` est ``1``.
Observons quelques exemples faisant appel aux valeurs par défaut dans un contexte LTR.
.. code-block:: python
>>> s[0:7] # d et f sont explicites. p est implicite
'Hello W'
>>> s[0:9:2] # d, f et p sont explicites
'HloWr'
>>> s[:7] # f est explicite. d et p sont implicites
'Hello W'
>>> s[6:] # d est explicite. f et p sont implicites
'World !'
>>> s[::3] # d et f sont implicites. p est explicite
'HlWl!'
.. admonition:: A expérimenter...
Chacun des 3 index utilisés pour le slicing peut prendre une valeur explicite ou par défaut. Ce qui donne 8 possibilités. Pour le parcours LTR, 5 sont présentées ci dessus. Identifier les 3 autres cas, les mettre en oeuvre sur un exemple, conjecturer le résultat et vérifier dans l'interpréteur interactif.
Pour un parcours de droite à gauche (Right To Left : RTL), pour lequel ``f ≤ d`` :
- la valeur par défaut de ``d`` est ``len(s)-1`` ;
- la valeur par défaut de ``f`` est ``0`` ;
- la valeur par défaut de ``p`` est ``-1``.
Observons quelques exemples faisant appel aux valeurs par défaut dans un contexte RTL.
.. code-block:: python
>>> s[-1:-8:-1] # d, f et p sont explicites
'! dlroW'
>>> s[:-8:-1] # f et p sont explicites. d est implicite
'! dlroW'
>>> s[-9::-1] # d et p sont explicites. f est implicite
'olleH'
>>> s[::-3] # d et f sont implicites. p est explicite
'!lWlH'
.. admonition:: A expérimenter...
Chacun des 3 index utilisés pour le slicing peut prendre une valeur explicite ou par défaut. Ce qui donne 8 possibilités. Pour le parcours RTL, 4 sont présentées ci dessus. Identifier les 4 autres cas, les mettre en oeuvre sur un exemple, conjecturer le résultat et vérifier dans l'interpréteur interactif.
Cas particulier : la syntaxe ``s[::-1]`` permet de renverser la chaine de caractère en une seule opération:
.. code-block:: python
>>> s = "anticonstitutionnellement"
>>> s[::-1] # la chaîne inversée
'tnemellennoitutitsnocitna'
Les chaînes de caractère sont des séquences *immutables* ce qui signifie qu'on ne peut pas les modifier. Il faut alors créer une autre chaîne.
.. code-block:: python
>>> s[6] = 'w'
Traceback (most recent call last):
File "", line 1, in
TypeError: 'str' object does not support item assignment
>>> s2 = s[:6] + 'w' + s[7:]
>>> s2
'Hello world !'
Itérer sur une chaîne de caractères
-----------------------------------
Comme on vient de le voir, en Python les chaînes de caractères sont des séquences. Python dispose d'un opérateur commun à toutes les séquences, que l'on retrouvera par la suite. Il s'agit de l'opérateur ``in``. Il a deux grands rôles majeurs.
Le premier est le test d'appartenance.
.. code-block:: python
>>> s = "anticonstitutionnellement"
>>> 'z' in s
False
>>> 'a' in s
True
Il permet également d'itérer de façon simple sur les chaînes de caractères.
.. code-block:: python
>>> s = "alpha"
>>> for c in s:
... print(c)
...
a
l
p
h
a
L'opérateur ``in`` sera utilisé dans bien d'autres strutures de données, comme on aura l'occasion de le voir par la suite.
Les méthodes de chaîne
----------------------
Tout est objet en Python et on dispose d'un grand nombre de `méthodes de chaines `_.
Chacune de ces méthodes peut être appelée sur une chaîne de caractères : Quelques exemples...
.. code-block:: python
>>> s = "anticonstitutionnellement"
>>> s.capitalize()
'Anticonstitutionnellement'
>>> s.upper()
'ANTICONSTITUTIONNELLEMENT'
>>> s.find('c')
4
Lorsque l'on doit effectuer une opération sur un objet en général, et sur des chaînes de caractères en particulier, la bonne démarche est de se documenter sur les méthodes disponibles pour l'objet considéré. Si le traitement est complexe et ne peut être réalisé à partir des méthodes fournies par le langage, alors on peut penser à implémenter ses propres fonctions, le plus souvent en se servant des méthodes *built-in* comme briques de base.
.. admonition:: A expérimenter...
Mettre en oeuvre quelques méthodes de chaines de caractères pour se familiariser avec la démarche et manipuler la documentation.
.. _python-strings-formatting:
Formatage
---------
Pour un affichage optimal, il est souvent nécessaire de formatter la chaîne de caractères (:class:`~str`). Python dispose d'un mécanisme appelé f-strings qui permet de formater les chaînes de caractères de manière concise et lisible.
Une f-string possède la structure suivante : ``f"texte {expression} texte"``. Les accolades ``{}`` permettent d'insérer des expressions Python dans la chaîne de caractères. Ces expressions sont évaluées et leur valeur est insérée dans la chaîne de caractères.
Par exemple, pour afficher la valeur de PI avec 3 décimales, on peut utiliser une f-string comme suit :
.. code-block:: python
>>> import math
>>> f"La valeur de PI arrondie : {math.pi:.3f}"
'La valeur de PI arrondie : 3.142'
Ici ``math.pi`` est l'expression évaluée, et ``:.3f`` est un format spécifiant que la valeur doit être affichée avec 3 décimales.
Pour plus d'informations sur le formatage des chaînes de caractères, on consultera `la documentation officielle de Python sur les f-strings `_.
Ce qu'il faut retenir
---------------------
.. quiz:: quizz-05
:title: Les chaines de caractères
- :quiz:`{"type":"TF","answer":"T"}` Une chaine de caractère peut se délimiter avec ``'``
- :quiz:`{"type":"TF","answer":"T"}` Une chaine de caractère peut se délimiter avec ``"``
- :quiz:`{"type":"TF","answer":"T"}` Une chaine de caractère peut se délimiter avec ``"""``
- :quiz:`{"type":"TF","answer":"T"}` Une chaine de caractère délimitée par ``'`` peut inclure des sauts de ligne mais il faut utiliser un caractère spécial
- :quiz:`{"type":"TF","answer":"T"}` Une chaine de caractère délimitée par ``"`` peut inclure des sauts de ligne mais il faut utiliser un caractère spécial
- :quiz:`{"type":"TF","answer":"F"}` Une chaine de caractère délimitée par ``"""`` peut inclure des sauts de ligne mais il faut utiliser un caractère spécial
- :quiz:`{"type":"TF","answer":"F"}` Le test d'égalité utilise l'opérateur ``is``
- :quiz:`{"type":"TF","answer":"F"}` Le test d'identité utilise l'opérateur ``==``
- :quiz:`{"type":"TF","answer":"F"}` Pour les opérations de slicing, l'index définit le caractère
- :quiz:`{"type":"TF","answer":"T"}` Pour les opérations de slicing, l'index définit l'intervalle entre les caractères
- :quiz:`{"type":"TF","answer":"T"}` Pour les opérations de slicing, les index et le pas peuvent être positifs
- :quiz:`{"type":"TF","answer":"T"}` Pour les opérations de slicing, les index et le pas peuvent être négatifs
- :quiz:`{"type":"TF","answer":"F"}` Pour les opérations de slicing, les index et le pas peuvent être nuls
- :quiz:`{"type":"TF","answer":"T"}` Pour les opérations de slicing, les index et le pas peuvent être implicites
- :quiz:`{"type":"TF","answer":"F"}` Pour les opérations de slicing, si l'index est positif, on sélectionne le caractère immédiatement à gauche
- :quiz:`{"type":"TF","answer":"F"}` Pour les opérations de slicing, si l'index est négatif, on sélectionne le caractère immédiatement à droite
- :quiz:`{"type":"TF","answer":"T"}` Pour les opérations de slicing, si le pas est positif, on parcourt la chaine de gauche à droite (LTR)
- :quiz:`{"type":"TF","answer":"T"}` Pour les opérations de slicing, si le pas est négatif, on parcourt la chaine de droite à gauche (RTL)
- :quiz:`{"type":"TF","answer":"F"}` Itérer sur une chaine de caractère impose de manipuler les index
- :quiz:`{"type":"TF","answer":"F"}` Itérer sur une chaine de caractère impose de manipuler les caractères
- :quiz:`{"type":"TF","answer":"T"}` La bonne pratique lorsqu'on itère sur une chaine de caractère est de manipuler les caractères
- :quiz:`{"type":"TF","answer":"F"}` Le formatage d'une chaine de caractère permet de contraindre sa longueur
.. _exercice-palindromes:
.. exercice:: Palindromes
Il est nécessaire que VS Code soit démarré sur la machine hôte, pointe vers le répertoire ``e3-programmation-labs-student`` et que le container Docker soit lancé.
Le répertoire concerné est ``lab_python_palindromes``. Lisez attentivement le fichier ``README.md`` pour comprendre la consigne. Il est efficace d'y accéder depuis le dépôt distant pour profiter du formatage.
Une fois l'exercice terminé, effectuer une :ref:`revue de code `, ajouter la :ref:`documentation `, et s'assurer que les repos local et distant soient correctement :ref:`synchronisés `.
.. _python-lists:
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 ``]``.
.. code-block:: python
>>> 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:`python-strings-slicing` des chaines de caractères sont valables.
.. code-block:: python
>>> 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.
.. code-block:: python
>>> 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` ou les supprimer avec :meth:`~list.remove`.
.. code-block:: python
>>> l.insert(2,99)
>>> l
['zéro', 1, 99, 1, 2, 3, 5, 8, 13, 21, 34]
>>> 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.
.. code-block:: python
>>> 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.
.. code-block:: python
>>> 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`.
.. code-block:: python
>>> 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.
.. code-block:: python
>>> l.extend([89, 144])
>>> 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, la suppression et la modification d'éléments dans une liste.
.. code-block:: python
>>> # 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]
>>> # suppression
>>> l[2:5] = []
>>> l
[0, 1, 5, 8, 13, 21, 34]
>>> # 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 ``in``.
.. code-block:: python
>>> 34 in l
True
>>> 63 in l
False
Itérer sur une liste
--------------------
L'opérateur ``in`` est également utilisé pour l'itération.
.. code-block:: python
>>> 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.
.. code-block:: python
>>> 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.
.. code-block:: python
>>> 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 de séquences `_ pour manipuler les séquences dont les listes font partie.
On peut par exemple effectuer une recherche avec :meth:`~list.index` ou inverser la :class:`~list` avec :meth:`~list.reverse`.
.. code-block:: python
>>> l.index(89)
10
>>> 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`.
.. code-block:: python
>>> 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`.
.. code-block:: python
>>> 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.
.. code-block:: python
>>> 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.
.. code-block:: python
>>> 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. On parle alors de listes de listes. C'est une structure très pratique pour représenter des matrices ou des tableaux multidimensionnels.
.. code-block:: python
>>> 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`.
.. code-block:: python
>>> type(nums[1])
>>> nums[1]
[1, 3, 5, 7, 9]
>>> type(nums[1][3])
>>> nums[1][3]
7
Unpacking
---------
Le *list unpacking* consiste à décomposer une liste (ou une autre séquence) en plusieurs variables, en une seule instruction. C'est une manière pratique d'extraire des éléments sans devoir utiliser des index.
Unpacking simple
................
Le nombre d'éléments à gauche du signe égal (LHS) correspond au nombre d'éléments de la liste.
.. code-block:: python
>>> a, b, c = [10, 20, 30]
Ici :
- a reçoit 10
- b reçoit 20
- c reçoit 30
C'est équivalent aux instructions.
.. code-block:: python
>>> a = 10
>>> b = 20
>>> c = 30
Unpacking avec trop peu ou trop d'éléments
..........................................
Les deux côtés du signe égal doivent contenir exactement le même nombre d'éléments.
.. code-block:: python
>>> a, b = [1, 2, 3] # ❌ Erreur : trop d'éléments
>>> a, b, c = [1, 2] # ❌ Erreur : trop peu d'éléments
Gérer des tailles variables
...........................
Python fournit un outil très pratique : l'opérateur ``*``, qui permet de capturer plusieurs éléments dans une seule variable.
.. code-block:: python
>>> head, *tail = [1, 2, 3, 4]
>>> head
1
>>> tail
[2, 3, 4]
L'opérateur ``*`` peut être en début, au milieu ou à la fin. Au début, il capture le dernier élément d'abord et capture ensuite une séquence de longueur variable.
.. code-block:: python
>>> *start, last = [1, 2, 3, 4]
>>> start
[1, 2, 3]
>>> last
4
Lorsque l'opérateur ``*`` est au milieu, il capture le premier et le dernier élément puis la séquence de longueur variable.
.. code-block:: python
>>> first, *middle, last = [1, 2, 3, 4, 5]
>>> first
1
>>> middle
[2, 3, 4]
>>> last
5
Lorsque l'opérateur ``*`` est seul.
.. code-block:: python
>>> *a, = [1, 2, 3]
>>> a
[1, 2, 3]
Quand utiliser le list unpacking ?
..................................
Voilà quelques raisons d'utiliser le *list unpacking* :
- extraire facilement les premiers ou derniers éléments d'une liste ;
- écrire du code plus lisible ;
- parcourir des structures récursives ;
- décomposer des tuples retournés par des fonctions.
Les list comprehension
----------------------
On peut construire une :class:`~list` à partir d'une boucle ``for`` et de la méthode :meth:`~list.append`.
.. code-block:: python
>>> 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``.
.. code-block:: python
>>> cubes = [ i**3 for i in range(10)]
>>> cubes
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
La variable utilisée à l'intérieur de la ``list comprehension`` est locale à celle ci.
.. code-block:: python
>>> 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 ou d'imbriquer des boucles, qui sont exécutées dans l'ordre d'apparition dans la ``list comprehension`` ou bien encore de construire des listes de listes.
>>> [ i**3 for i in range(10) if i%2 == 0]
[0, 8, 64, 216, 512]
>>> [i+str(j) for i in 'ab' for j in range(3)]
['a0', 'a1', 'a2', 'b0', 'b1', 'b2']
>>> [[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``.
.. code-block:: python
>>> 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 et on retrouve ici la notion de pointeurs en C.
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::
.. code-block:: python
>>> l1 = [0, 1, 2, 3, 4]
>>> 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``.
.. code-block:: python
>>> 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.
.. code-block:: python
>>> 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`.
.. code-block:: python
>>> 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`` fonctionne 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
.. _exercice-syracuse:
.. exercice:: Conjecture de Syracuse
Il est nécessaire que VS Code soit démarré sur la machine hôte, pointe vers le répertoire ``e3-programmation-labs-student`` et que le container Docker soit lancé.
Le répertoire concerné est ``lab_python_syracuse``. Lisez attentivement le fichier ``README.md`` pour comprendre la consigne. Il est efficace d'y accéder depuis le dépôt distant pour profiter du formatage.
Une fois l'exercice terminé, effectuer une :ref:`revue de code `, ajouter la :ref:`documentation `, et s'assurer que les repos local et distant soient correctement :ref:`synchronisés `.
.. _python-tuples:
Les tuples
==========
Une vidéo de présentation des tuples...
.. raw:: html
Contrairement à la :class:`list`, le :class:`~tuple` est une séquence *immutable*. Comme la liste, c'est une collection ordonnée. Il est défini par les opérateurs ``(`` et ``)``. Bien que le langage le permette, **il n'est pas conseillé d'utiliser une liste pour stocker des collections hétérogènes**. Cet usage est plutôt réservé au :class:`~tuple`.
.. code-block:: python
>>> ev = ('Everest', 8848, 'Asie', 'Himalaya')
>>> type(ev)
>>> len(ev)
4
Slicing
-------
Le :class:`tuple` étant une **sequence**, il dispose des mêmes opérations que la :class:`list`, à l'exception des opérations de modification puisque le :class:`tuple` est *immutable*.
.. code-block:: python
>>> # indexation
>>> ev[0]
'Everest'
>>> ev[1]
8848
>>> # slicing
>>> ev[:2]
('Everest', 8848)
Le tuple est immutable
----------------------
Le :class:`tuple` est *immutable* et toute opération tentant à le modifier déclenche une erreur.
.. code-block:: python
>>> # modification d'un élément
>>> ev[1] = 8849
Traceback (most recent call last):
File "", line 1, in
TypeError: 'tuple' object does not support item assignment
>>> # suppression d'un élément
>>> del(ev[2])
Traceback (most recent call last):
File "", line 1, in
TypeError: 'tuple' object doesn't support item deletion
L'opération d'indexation d'un :class:`tuple` ne peut apparaître qu'à droite (RHS) de l'opérateur d'affectation ``=``.
Appartenance
------------
Le :class:`tuple` dispose de toutes les méthodes de **sequence**. On peut en particulier utiliser l'opérateur ``in`` pour tester l'appartenance.
.. code-block:: python
>>> 'Everest' in ev
True
>>> 'Kilimandjaro' in ev
False
Itérer sur un tuple
-------------------
Comme la :class:`~list`, le :class:`~tuple` est une séquence et l'opérateur ``in`` est utilisé pour l'itération.
.. code-block:: python
>>> for elt in ev:
... print(elt)
...
Everest
8848
Asie
Himalaya
Les méthodes de :class:`~tuple`
-------------------------------
Le :class:`~tuple` étant une séquence immutable, les interactions possibles avec celui ci sont limitées.
.. admonition:: A expérimenter...
Même si le nombre de méthodes d'un :class:`~tuple` est limité, il en existe cependant deux. Trouvez les, de deux façons différentes:
- en utilisant une fonction Python dans l'interpréteur interactif ;
- en recherchant dans `la documentation officielle `_.
Listes de tuples
----------------
Comme on l'a vu précédemment, la :class:`~list` peut contenir tout type de donnée, et en particulier on peut construire une liste de :class:`~tuple`.
.. code-block:: python
>>> ac = ('Aconcagua', 6959, 'Amérique du Sud', 'Cordillère des Andes')
>>> mc = ('McKinley', 6190, 'Amérique du Nord', 'Alaska')
>>> ki = ('Kilimandjaro', 5892, 'Afrique', 'Vallée du Grand Rift')
>>> sommets = [ev, ac, mc, ki]
>>> sommets
[('Everest', 8848, 'Asie', 'Himalaya'),
('Aconcagua', 6959, 'Amérique du Sud', 'Cordillère des Andes'),
('McKinley', 6190, 'Amérique du Nord', 'Alaska'),
('Kilimandjaro', 5892, 'Afrique', 'Vallée du Grand Rift')]
>>> # indexation en profondeur
>>> sommets[2][3]
'Alaska'
Les tuples nommés
-----------------
Le :class:`~tuple` est parfaitement adapté à la création d'une structure de données complexe, à l'image des structures du langage C. Cependant l'accès à chacun des champs à partir de l'index (comme on vient de le voir) n'est pas très commode. Le module :mod:`collections` dispose d'un :func:`~collections.namedtuple` autorisant un accès facilité aux éléments.
.. code-block:: python
>>> from collections import namedtuple
>>> Sommet = namedtuple('Sommet', ['nom', 'alt', 'pays', 'chaine'])
>>> ev = Sommet('Everest', 8848, 'Asie', 'Himalaya')
>>> ev
Sommet(nom='Everest', alt=8848, pays='Asie', chaine='Himalaya')
>>> ev.nom
'Everest'
>>> ev.chaine
'Himalaya'
Ce qu'il faut retenir
---------------------
.. quiz:: quizz-07
:title: Les tuples
- :quiz:`{"type":"TF","answer":"F"}` Un tuple se délimite avec ``[`` et ``]``
- :quiz:`{"type":"TF","answer":"T"}` Un tuple se délimite avec ``(`` et ``)``
- :quiz:`{"type":"TF","answer":"F"}` Un tuple se délimite avec ``{`` et ``}``
- :quiz:`{"type":"TF","answer":"T"}` Un tuple peut contenir des objets hétérogènes
- :quiz:`{"type":"TF","answer":"T"}` Un tuple est une collection ordonnée
- :quiz:`{"type":"TF","answer":"F"}` Les opérations de slicing sur un tuple sont légèrement différentes des opérations de slicing sur une chaine de caractères
- :quiz:`{"type":"TF","answer":"F"}` Un tuple est *mutable*
- :quiz:`{"type":"TF","answer":"T"}` Une fois créé, la taille d'un tuple est figée
- :quiz:`{"type":"TF","answer":"F"}` On peut insérer un élément dans un tuple après sa création
- :quiz:`{"type":"TF","answer":"F"}` On peut supprimer un élément d'un tuple
- :quiz:`{"type":"TF","answer":"T"}` Un tuple dispose de méthodes
- :quiz:`{"type":"TF","answer":"T"}` Le slicing peut être utilisé pour extraire des éléments d'un tuple
- :quiz:`{"type":"TF","answer":"F"}` Le slicing peut être utilisé pour insérer des éléments dans un tuple
- :quiz:`{"type":"TF","answer":"T"}` Le slicing ne peut pas modifier la taille d'un tuple
- :quiz:`{"type":"TF","answer":"T"}` Sur un tuple, l'opérateur d'appartenance ``in`` fonctionne exactement comme pour une liste
- :quiz:`{"type":"TF","answer":"T"}` La façon privilégiée de parcourir un tuple est d'itérer sur ses éléments
- :quiz:`{"type":"TF","answer":"F"}` La façon privilégiée de parcourir un tuple est d'itérer sur l'index de ses éléments
- :quiz:`{"type":"TF","answer":"F"}` Un tuple dispose d'une méthode pour trier ses éléments
- :quiz:`{"type":"TF","answer":"T"}` On peut construire un tuple dont les éléments sont triés
- :quiz:`{"type":"TF","answer":"T"}` Un tuple peut contenir n'importe quel objet Python
- :quiz:`{"type":"TF","answer":"T"}` On peut construire des tuples imbriqués
.. _exercice-asciiart:
.. exercice:: ASCII Art
Il est nécessaire que VS Code soit démarré sur la machine hôte, pointe vers le répertoire ``e3-programmation-labs-student`` et que le container Docker soit lancé.
Le répertoire concerné est ``lab_python_asciiart``. Lisez attentivement le fichier ``README.md`` pour comprendre la consigne. Il est efficace d'y accéder depuis le dépôt distant pour profiter du formatage.
Une fois l'exercice terminé, effectuer une :ref:`revue de code `, ajouter la :ref:`documentation `, et s'assurer que les repos local et distant soient correctement :ref:`synchronisés `.
.. _python-sets:
Les sets
========
Une vidéo de présentation des ensembles...
.. raw:: html
Les :class:`~set` sont des containers mutables non ordonnés qui possèdent les caractéristiques des ensembles mathématiques. En particulier, ils ne disposent pas d'éléments dupliqués. Ils se définissent à partir des opérateurs ``{`` et ``}`` ou du constructeur :class:`~set`.L'ordonnancement n'a pas de sens pour un :class:`~set` et n'est pas prédictible. L'implémentation de l'algorithme de hachage utilisé pour stocker les éléments dans le :class:`~set` est optimisée pour la rapidité d'accès aux éléments, mais ne garantit pas un ordre particulier.
.. code-block:: python
>>> foot = set(['Angers', 'Bordeaux', 'Brest', 'Dijon', 'Lens', 'Lille', 'Lorient', 'Lyon', 'Marseille', 'Metz', 'Monaco', 'Montpellier', 'Nantes', 'Nice', 'Nimes', 'Paris', 'Reims', 'Rennes', 'Saint - Étienne', 'Strasbourg'])
>>> len(foot)
20
>>> foot
{'Marseille', 'Lille', 'Metz', 'Rennes', 'Nice', 'Nantes', 'Nimes', 'Strasbourg', 'Bordeaux', 'Dijon', 'Montpellier', 'Brest', 'Lorient', 'Reims', 'Lens', 'Paris', 'Monaco', 'Angers', 'Lyon', 'Saint - Étienne'}
Comme la :class:`~list`, le :class:`~set` est une séquence **mutable**. Il dispose donc de toutes les méthodes correspondantes à ce type de séquence. On peut donc en particulier le modifier une fois créé, par exemple retirer des éléments avec la méthode :meth:`~set.remove` ou en ajouter avec la méthode :meth:`~set.add`
.. code-block:: python
>>> foot.remove('Metz')
>>> len(foot)
19
>>> foot.add('Metz')
>>> len(foot)
20
.. note::
La sémantique est importante :
- Une :class:`~list` étant un container ordonné, ajouter un élément c'est le placer à la fin, et à cette fin, on utilise le verbe :meth:`~list.append` ;
- Un :class:`~set` étant un container non ordonné, ajouter un élément ne donne pas d'information de position, et à cette fin, on utilise le verbe :meth:`~set.add`.
Le constructeur :class:`~set` prend en argument une séquence (une :class:`~list` pour ce qui concerne l'exemple ci dessus). Cette séquence peut éventuellement contenir des éléments dupliqués. Les doublons sont automatiquement éliminés.
.. admonition:: A expérimenter...
Une chaîne de caractères (:class:`~str`) est une séquence et peut être utilisée pour construire un :class:`~set`. Conjecturer le résultat de l'opération suivante.
.. code-block:: python
>>> f = set('football')
Vérifier dans un interpréteur interactif.
Le :class:`~set` est un conteneur non ordonné. La position ou l'ordre d'insertion des éléments n'est pas prédictible. En conséquence, les opérations d'indexation ou de slicing ne sont pas supportées.
.. code-block:: python
>>> f[0]
Traceback (most recent call last):
File "", line 1, in
TypeError: 'set' object does not support indexing
Les :class:`~set` ne peuvent contenir que des objets immutables, dont la structure ne peut pas être modifiée après création. Ces objets sont dits "hashables". Les exemples ci dessus utilisent des chaînes de caractères. On peut également créer des :class:`~set` de nombres entiers mais si l'on tente d'insérer un objet mutable dans un :class:`~set`, une exception de type ``TypeError`` est générée.
.. code-block:: python
>>> s = {0, 1, 2, 3, 4}
>>> s
{0, 1, 2, 3, 4}
>>> s = {0, 1, 2, [3, 4]}
Traceback (most recent call last):
File "", line 1, in
TypeError: unhashable type: 'list'
Appartenance
------------
Comme toutes les séquences, les :class:`~set` disposent de l'opérateur ``in``.
.. code-block:: python
>>> basket = set(['Boulazac', 'Bourg-en-Bresse', 'Chalon/Saône', 'Cholet', 'Reims', 'Dijon', 'Gravelines', 'Levallois', 'Le Mans', 'Le Portel', 'Limoges', 'Monaco', 'Nanterre', 'Orléans', 'Pau', 'Roanne', 'Strasbourg', 'Villeurbane'])
>>> 'Limoges' in basket
True
>>> 'Limoges' in foot
False
Itérer sur un set
-----------------
Le :class:`~set` étant également une séquence, l'opérateur ``in`` est utilisé pour l'itération.
.. code-block:: python
>>> for elt in f:
... print(elt)
...
a
b
o
f
t
l
Puisque le :class:`~set` ne supporte pas l'indexation, on ne peut itérer que sur les éléments. Inutile donc ici de préciser une bonne pratique, contrairement aux :class:`~str`, :class:`~list` et :class:`~tuple`.
Opérations ensemblistes
-----------------------
Les :class:`~set` disposent d'opérations ensemblistes puissantes et rapides :
- intersection
- union
- différence
- inclusion
- etc...
La méthode :meth:`~set.intersection` est utilisée pour calculer l'intersection de deux ou plusieurs :class:`~set`. L'opérateur ``&`` permet une écriture plus lisible. La méthode :meth:`~set.union` est utilisée pour calculer l'union de deux ou plusieurs :class:`~set`. L'opérateur ``|`` permet une écriture plus lisible.
.. code-block:: python
>>> rugby = set(['Agen', 'Bayonne', 'Bordeaux', 'Brive', 'Castres', 'Clermont', 'La Rochelle', 'Lyon', 'Montpellier', 'Pau', 'Racing 92','Paris', 'Toulon', 'Toulouse'])
>>> foot & rugby # intersection
{'Paris', 'Montpellier', 'Bordeaux', 'Lyon'}
>>> foot | rugby # union
{'Marseille', 'Monaco', 'Lille', 'Metz', 'Dijon', 'Pau', 'Agen', 'Clermont', 'Montpellier', 'Racing 92', 'Rennes', 'Bayonne', 'Brive', 'Nice', 'Nantes', 'Brest', 'Toulouse', 'Lorient', 'Castres', 'Nimes', 'La Rochelle', 'Reims', 'Lens', 'Paris', 'Toulon', 'Strasbourg', 'Angers', 'Lyon', 'Saint - Étienne', 'Bordeaux'}
La méthode :meth:`~set.difference` est utilisée pour calculer la différence de deux ou plusieurs :class:`~set`. L'opérateur ``-`` permet une écriture plus lisible. La méthode :meth:`~set.symetric_difference` est utilisée pour calculer la différence symétrique (des éléments appartenant à l'un ou à l'autre mais pas aux deux) de deux :class:`~set`. L'opérateur ``^`` permet une écriture plus lisible.
.. code-block:: python
>>> rugby - foot - basket # difference
{'Toulouse', 'Castres', 'Agen', 'Clermont', 'La Rochelle', 'Racing 92', 'Toulon', 'Bayonne', 'Brive'}
>>> rugby ^ foot
{'Marseille', 'Monaco', 'Lille', 'Metz', 'Dijon', 'Pau', 'Agen', 'Clermont', 'Racing 92', 'Rennes', 'Bayonne', 'Brive', 'Nice', 'Nantes', 'Brest', 'Toulouse', 'Lorient', 'Castres', 'Nimes', 'La Rochelle', 'Reims', 'Lens', 'Toulon', 'Strasbourg', 'Angers', 'Saint - Étienne'}
Pour une meilleure compréhension de la manipulation des :class:`~set`, on consultera `les méthodes de set `_ dédiées.
Les set comprehension
---------------------
Comme pour :ref:`python-lists`, on peut construire un :class:`~set` de façon concise avec un ``set comprehension``.
.. code-block:: python
>>> {x*y for x in {1,2,3} for y in {2,3,4} if x != y}
{2, 3, 4, 6, 8, 12}
Chaque fois que ce sera possible cette syntaxe devra être utilisée.
Les frozensets
--------------
Il peut être utile de disposer d'un :class:`~set` *immutable*. C'est le rôle du type :class:`~frozenset`. Il se construit à partir d'une séquence, comme le :class:`~set`. Il ne dispose pas de méthodes pour ajouter ou supprimer des éléments. Il est donc *immutable*.
Ce qu'il faut retenir
---------------------
.. quiz:: quizz-09
:title: Les sets
- :quiz:`{"type":"TF","answer":"F"}` Un set se délimite avec ``[`` et ``]``
- :quiz:`{"type":"TF","answer":"F"}` Un set se délimite avec ``(`` et ``)``
- :quiz:`{"type":"TF","answer":"T"}` Un set se délimite avec ``{`` et ``}``
- :quiz:`{"type":"TF","answer":"T"}` Un set peut contenir des objets hétérogènes
- :quiz:`{"type":"TF","answer":"F"}` Un set peut contenir des objets *mutables*
- :quiz:`{"type":"TF","answer":"T"}` Un set est *mutable*
- :quiz:`{"type":"TF","answer":"F"}` Un set est une collection ordonnée
- :quiz:`{"type":"TF","answer":"F"}` Les opérations de slicing sur un set sont possibles
- :quiz:`{"type":"TF","answer":"F"}` Une fois créée, la taille d'un set est figée
- :quiz:`{"type":"FB","answer":"add", "size":5}` est une méthode permettant d'insérer un élément dans un set
- :quiz:`{"type":"TF","answer":"F"}` Il est possible d'insérer un élément à la fin d'un set
- :quiz:`{"type":"FB","answer":"remove", "size":5}` est une méthode permettant de supprimer un élément d'un set
- :quiz:`{"type":"TF","answer":"T"}` Le constructeur d'un set prend en paramètre une séquence
- :quiz:`{"type":"TF","answer":"F"}` Le slicing peut être utilisé pour extraire des éléments d'un set
- :quiz:`{"type":"TF","answer":"F"}` Le slicing peut être utilisé pour insérer des éléments dans un set
- :quiz:`{"type":"TF","answer":"T"}` Sur un set, l'opérateur d'appartenance ``in`` fonctionne comme pour toute séquence
- :quiz:`{"type":"TF","answer":"T"}` On peut parcourir un set en itérant sur ses éléments
- :quiz:`{"type":"TF","answer":"F"}` On peut parcourir un set en itérant sur l'index de ses éléments
- :quiz:`{"type":"TF","answer":"F"}` Un set peut contenir n'importe quel objet Python
- :quiz:`{"type":"TF","answer":"F"}` On peut construire des sets imbriqués
- Un :quiz:`{"type":"FB","answer":"set comprehension", "size":18}` est une façon concise de construire un set
.. _exercice-langue-francaise:
.. exercice:: Les mots de la langue française
Il est nécessaire que VS Code soit démarré sur la machine hôte, pointe vers le répertoire ``e3-programmation-labs-student`` et que le container Docker soit lancé.
Le répertoire concerné est ``lab_python_langue_francaise``. Lisez attentivement le fichier ``README.md`` pour comprendre la consigne. Il est efficace d'y accéder depuis le dépôt distant pour profiter du formatage.
Une fois l'exercice terminé, effectuer une :ref:`revue de code `, ajouter la :ref:`documentation `, et s'assurer que les repos local et distant soient correctement :ref:`synchronisés `.
.. _python-dicts:
Les dictionnaires
=================
Une vidéo de présentation des dictionnaires...
.. raw:: html
Comme on l'a vu, Python dispose d'un grand nombre de séquences. Tous les langages modernes disposent également d'un ou plusieurs tableaux associatifs. En Python il prend le nom de dictionnaire. Il se définit à partir des opérateurs ``{`` et ``}`` ou du constructeur de la classe :class:`~dict`. Le dictionnaire est un container **mutable**.
Structure
---------
Un dictionnaire est constitué d'un ensemble de paires ``clé:valeur``. La clé doit être un élément **immutable**. Un :class:`~dict` conserve l'ordre dans lequel les paires ``clé:valeur`` ont été insérées.
.. code-block:: python
>>> d = dict()
>>> type(d)
On ajoute des éléments à un :class:`~dict` avec l'opérateur d'indexation ``[ ]``.
.. code-block:: python
>>> d['Italy'] = 'Rome'
>>> d['France'] = 'Paris'
>>> d['Spain'] = 'Madrid'
>>> d
{'Italy': 'Rome', 'France': 'Paris', 'Spain': 'Madrid'}
Un :class:`~dict` possède un ensemble de méthodes. La méthode :meth:`~dict.keys` retourne une vue des clés du dictionnaire et la méthode :meth:`~dict.values` retourne une vue des valeurs associées.
.. code-block:: python
>>> dir(d)
[..., 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']
>>> d.keys()
dict_keys(['Italy', 'France', 'Spain'])
>>> d.values()
dict_values(['Rome', 'Paris', 'Madrid'])
Les vues sont des objets *itérables*.
.. code-block:: python
>>> for key in d.keys():
... print(key)
...
Italy
France
Spain
On peut également créer un :class:`~dict` à partir d'une **sequence** de paires ``clé:valeur``. Cette séquence peut être une :class:`~list` de :class:`~tuple` ou un :class:`~tuple` de :class:`~tuple`
.. code-block:: python
>>> d = dict([('Italy','Rome'), ('Spain','Madrid'), ('France','Paris')])
>>> d
{'Spain': 'Madrid', 'Italy': 'Rome', 'France': 'Paris'}
>>> d = dict((('Italy','Rome'), ('Spain','Madrid'), ('France','Paris')))
>>> d
{'Spain': 'Madrid', 'Italy': 'Rome', 'France': 'Paris'}
Accès aux éléments
------------------
Si ``d`` est un :class:`~dict`, on accède à ses éléments avec la syntaxe ``d[key]``. Si l'élément existe, il est retourné. S'il n'existe pas, une erreur se produit.
.. code-block:: python
>>> d['Italy']
'Rome'
>>> d['Portugal']
Traceback (most recent call last):
File "", line 1, in
KeyError: 'Portugal'
:meth:`~dict.get` est une méthode plus sûre. Si l'élément existe, il est retourné. S'il n'existe pas, une valeur par défaut est utilisée. Si elle n'est pas précisée, la valeur par défaut est ``None``.
.. code-block:: python
>>> d.get('Portugal')
>>> d.get('Portugal', 'key not in dict')
'key not in dict'
Modification de la structure d'un dictionnaire
----------------------------------------------
Le :class:`~dict` étant un élément **mutable**, on peut ajouter des éléments avec la syntaxe ``d[key] = value``. On peut également en retirer avec la syntaxe ``del d[key]``. On peut également utiliser la méthode :meth:`~dict.update` qui prend en argument un second dictionnaire. Le dictionnaire sur lequel la méthode est appelée est modifié avec les paires ``clé:valeur`` qu'il contient.
.. code-block:: python
>>> d['Portugal'] = 'Lisbon'
>>> d
{'Portugal': 'Lisbon', 'Italy': 'Rome', 'France': 'Paris', 'Spain': 'Madrid'}
>>> del d['Spain']
>>> d
{'Portugal': 'Lisbon', 'Italy': 'Rome', 'France': 'Paris'}
>>> d.update({'Germany':'Berlin', 'United Kingdom':'London'})
>>> d
{'Italy': 'Rome', 'France': 'Paris', 'Portugal': 'Lisbon', 'Germany': 'Berlin', 'United Kingdom': 'London'}
.. admonition:: A expérimenter...
Ajouter quelques paires clé-valeur au dictionnaire ci dessus avec l'opérateur d'indexation ``[ ]``. Puis de façon groupée à partir d'un second dictionnaire.
Itérer sur le dictionnaire ainsi créé en utilisant le :ref:`python-strings-formatting` des chaines de caractères pour produire un affichage structuré de l'association capitale-pays.
Appartenance
------------
Comme les **sequences** le :class:`~dict` utilise l'opérateur ``in`` pour tester l'appartenance d'une clé ou d'une valeur. Tester l'appartenance au dictionnaire lui même fonctionne pour les clés mais pas pour les valeurs.
.. code-block:: python
>>> 'Italy' in d.keys()
True
>>> 'Rome' in d.values()
True
>>> 'Italy' in d
True
>>> 'Rome' in d
False
Itérer sur un dictionnaire
--------------------------
La méthode :meth:`~dict.keys` retournant un iterable, on peut utiliser l'opérateur ``in`` pour itérer sur les clés.
.. code-block:: python
>>> for k in d.keys():
... print(k)
...
United Kingdom
Italy
Germany
Portugal
France
On a constaté plus haut que la méthode :meth:`~dict.keys` retourne une vue des clés du dictionnaire. Cette vue est **dynamique**, c'est à dire qu'elle évolue tout au long de la vie du dictionnaire.
.. code-block:: python
>>> keys = d.keys()
>>> keys
dict_keys(['United Kingdom', 'Italy', 'Germany', 'Portugal', 'France'])
>>> del d['Italy']
>>> keys
dict_keys(['United Kingdom', 'Germany', 'Portugal', 'France'])
Au delà des clés et des valeurs, on peut récupérer la paire ``clé-valeur`` dans un :class:`~tuple` en une seule opération avec la méthode :meth:`~dict.items`.
.. code-block:: python
>>> for k,v in d.items():
... print(k,v)
...
United Kingdom London
Germany Berlin
Portugal Lisbon
France Paris
.. important::
Pour une connaissance approfondie des dictionnaires, on consultera la liste des `méthodes `_ associées.
Les 'dict comprehension'
------------------------
Comme pour les :class:`~list` et les :class:`~set`, il existe une syntaxe compacte pour construire un :class:`~dict` : les ``dict comprehension``. L'exemple ci dessous donne le code ASCII des caractères de ponctuation.
.. code-block:: python
>>> import string
>>> { char:ord(char) for char in string.punctuation}
{']': 93, '&': 38, '~': 126, ... , '/': 47}
La sortie a été tronquée pour l'affichage
.. _python-switch:
Implémentation de ``switch``
----------------------------
Python ne dispose pas en standard de l'instruction ``switch`` mais une implémentation élégante et compacte est possible avec un dictionnaire.
.. code-block:: python
>>> d = {'a':'Choix A', 'b':'Choix B', 'c':'Choix C'}
>>> choix = 'b'
>>> print(d[choix])
L'intérêt est de grouper les différents cas sur une seule ligne. Comme en Python tout est objet, les valeurs du dictionnaire peuvent être des fonctions.
A titre d'exemple, l'utilisation de ``switch`` en Java:
.. code-block:: java
:linenos:
public class SwitchDemo {
public static void main(String[] args) {
String choix = 'b';
String res;
switch (choix) {
case 'a': res = "Choix A";
break;
case 'b': res = "Choix B";
break;
case 'c': res = "Choix C";
break;
}
System.out.println(res);
}
}
Ce qu'il faut retenir
---------------------
.. quiz:: quizz-10
:title: Les dictionnaires
- :quiz:`{"type":"TF","answer":"F"}` Un dictionnaire se délimite avec ``[`` et ``]``
- :quiz:`{"type":"TF","answer":"F"}` Un dictionnaire se délimite avec ``(`` et ``)``
- :quiz:`{"type":"TF","answer":"T"}` Un dictionnaire se délimite avec ``{`` et ``}``
- :quiz:`{"type":"TF","answer":"T"}` Les clés d'un dictionnaire sont obligatoirement *immutable*
- :quiz:`{"type":"TF","answer":"F"}` Les clés d'un dictionnaire sont toutes de même type
- :quiz:`{"type":"TF","answer":"T"}` On peut utiliser des chaines de caractères comme clés d'un dictionnaire
- :quiz:`{"type":"TF","answer":"T"}` On peut utiliser des entiers comme clés d'un dictionnaire
- :quiz:`{"type":"TF","answer":"T"}` On peut utiliser des tuples comme clés d'un dictionnaire
- :quiz:`{"type":"TF","answer":"F"}` On peut utiliser des listes comme clés d'un dictionnaire
- :quiz:`{"type":"TF","answer":"F"}` On peut utiliser des sets comme clés d'un dictionnaire
- :quiz:`{"type":"TF","answer":"F"}` Les valeurs d'un dictionnaire sont toutes de même type
- :quiz:`{"type":"TF","answer":"F"}` Les valeurs d'un dictionnaire sont obligatoirement *immutable*
- :quiz:`{"type":"TF","answer":"T"}` Un dictionnaire est *mutable*
- :quiz:`{"type":"TF","answer":"F"}` Un dictionnaire est une collection ordonnée
- :quiz:`{"type":"TF","answer":"F"}` Une fois créée, la taille d'un dictionnaire est figée
- :quiz:`{"type":"TF","answer":"F"}` L'opérateur d'indexation permet d'insérer un élément dans un dictionnaire
- :quiz:`{"type":"TF","answer":"F"}` L'opérateur d'indexation permet d'insérer un élément au début d'un dictionnaire
- :quiz:`{"type":"TF","answer":"F"}` L'opérateur d'indexation permet d'insérer un élément à la fin d'un dictionnaire
- :quiz:`{"type":"FB","answer":"del", "size":5}` permet de supprimer un élément d'un dictionnaire
- :quiz:`{"type":"TF","answer":"T"}` Le constructeur d'un dictionnaire peut prendre en paramètre une séquence
- :quiz:`{"type":"TF","answer":"F"}` Cette séquence est une séquence de clés
- :quiz:`{"type":"TF","answer":"F"}` Cette séquence est une séquence de valeurs
- :quiz:`{"type":"TF","answer":"T"}` Cette séquence est une séquence de paires ``clé:valeur``
- La méthode :quiz:`{"type":"FB","answer":"update", "size":5}` permet de modifier un dictionnaire avec un autre dictionnaire
- :quiz:`{"type":"TF","answer":"T"}` Sur un dictionnaire, l'opérateur d'appartenance ``in`` fonctionne pour les clés
- :quiz:`{"type":"TF","answer":"F"}` Sur un dictionnaire, l'opérateur d'appartenance ``in`` fonctionne pour les valeurs
- :quiz:`{"type":"TF","answer":"T"}` On peut parcourir un dictionnaire en itérant sur ses clés
- :quiz:`{"type":"TF","answer":"F"}` On peut parcourir un dictionnaire en itérant sur ses valeurs
- :quiz:`{"type":"TF","answer":"T"}` On peut construire des dictionnaires imbriqués
- Un :quiz:`{"type":"FB","answer":"dict comprehension", "size":18}` est une façon concise de construire un dictionnaire