Python w3
Python w3
https ://www.fun-mooc.fr
2
Page
i
Chapitre 3
3.1 w3-s1-c1-fichiers
Les fichiers
— 'r' (la chaîne contenant l’unique caractère r) pour ouvrir un fichier en lecture seulement ;
— 'w' en écriture seulement ; le contenu précédent du fichier, s’il existait, est perdu ;
— 'a' en écriture seulement ; mais pour ajouter du contenu en fin de fichier.
Voici par exemple comment on pourrait ajouter deux lignes de texte dans le fichier foo.txt qui contient,
à ce stade du notebook, deux entiers :
1
MOOC Python 3 Semaine 3, Séquence 1
0
1
100
101
Ces variantes sont décrites dans la section sur la fonction built-in open dans la documentation Python.
True
Par conséquent, écrire deux boucles for imbriquées sur le même objet fichier ne fonctionnerait pas comme
on pourrait s’y attendre.
0 x 1
0 x 100
0 x 101
w3-s1-c1-fichiers 2
MOOC Python 3 Semaine 3, Séquence 1
Digression - repr()
Comme nous allons utiliser maintenant des outils d’assez bas niveau pour lire du texte, pour examiner
ce texte nous allons utiliser la fonction repr(), et voici pourquoi :
abc
def
'abc\ndef\n'
Contenu complet
0
1
100
101
Bloc 0 >>'0\n1\n'<<
Bloc 1 >>'100\n'<<
On voit donc que chaque bloc contient bien quatre caractères en comptant les sauts de ligne :
bloc # contenu
0 un 0, un newline, un 1, un newline
1 un 1, deux 0, un newline
w3-s1-c1-fichiers 3
MOOC Python 3 Semaine 3, Séquence 1
La méthode flush
Les entrées-sorties sur fichier sont bien souvent bufferisées par le système d’exploitation. Cela signi-
fie qu’un appel à write ne provoque pas forcément une écriture immédiate, car pour des raisons de
performance on attend d’avoir suffisamment de matière avant d’écrire sur le disque.
Il y a des cas où ce comportement peut s’avérer gênant, et où on a besoin d’écrire immédiatement (et
donc de vider le buffer), et c’est le propos de la méthode flush.
Les fichiers que nous avons vus jusqu’ici étaient ouverts en mode textuel (c’est le défaut), et c’est pourquoi
nous avons interagi avec eux avec des objets de type str :
— ouvrir le fichier en mode binaire - pour cela on ajoute le caractère b au mode d’ouverture ;
— et on peut alors interagir avec le fichier avec des objets de type bytes.
w3-s1-c1-fichiers 4
MOOC Python 3 Semaine 3, Séquence 1
1 → 'Ã' [0xc3]
2 → '©' [0xa9]
3 → 'j' [0x6a]
4 → 'Ã' [0xc3]
5 → '\xa0' [0xa0]
6 → ' ' [0x20]
7 → 'l' [0x6c]
8 → "'" [0x27]
9 → 'Ã' [0xc3]
10 → '©' [0xa9]
11 → 't' [0x74]
12 → 'Ã' [0xc3]
13 → '©' [0xa9]
14 → '\n' [0xa]
Vous retrouvez ainsi le fait que l’unique caractère Unicode é a été encodé par UTF-8 sous la forme de
deux octets de code hexadécimal 0xc3 et 0xa9.
Vous pouvez également consulter ce site qui visualise l’encodage UTF-8, avec notre séquence d’entrée :
https://mothereff.in/utf-8#d%C3%A9j%C3%A0%20l%27%C3%A9t%C3%A9%0A
Ce qui correspond au fait que nos quatre caractères non-ASCII (3 x é et 1 x à) sont tous encodés par
UTF-8 comme deux octets, comme vous pouvez vous en assurer ici pour é et là pour à.
3.2 w3-s1-c2-utilitaires-sur-fichiers
Fichiers et utilitaires
— le module os.path, pour faire des calculs sur les chemins et noms de fichiers doc,
— le module os pour certaines fonctions complémentaires comme renommer ou détruire un fichier
doc,
w3-s1-c2-utilitaires-sur-fichiers 5
MOOC Python 3 Semaine 3, Séquence 1
— et enfin le module glob pour la recherche de fichiers, par exemple pour trouver tous les fichiers en
*.txt doc.
Cet ensemble un peu disparate a été remplacé par une librairie unique pathlib, qui fournit toutes ces
fonctionnalités sous un interface unique et moderne, que nous recommandons évidemment d’utiliser pour
du nouveau code.
Avant d’aborder pathlib, voici un très bref aperçu de ces trois anciens modules, pour le cas - assez
probable - où vous les rencontreriez dans du code existant ; tous les noms qui suivent correspondent à des
fonctions - par opposition à pathlib qui, comme nous allons le voir, offre une interface orientée objet :
— os.path.join ajoute ‘/’ ou ’' entre deux morceaux de chemin, selon l’OS
— os.path.basename trouve le nom de fichier dans un chemin
— os.path.dirname trouve le nom du directory dans un chemin
— os.path.abspath calcule un chemin absolu, c’est-à-dire à partir de la racine du filesystem
— os.remove (ou son ancien nom os.unlink), qui permet de supprimer un fichier
— os.rmdir pour supprimer un répertoire (mais qui doit être vide)
— os.removedirs pour supprimer tout un répertoire avec son contenu, récursivement si nécessaire
— os.rename pour renommer un fichier
Le module pathlib
C’est la méthode recommandée aujourd’hui pour travailler sur les fichiers et répertoires.
Orienté Objet
Comme on l’a mentionné pathlib offre une interface orientée objet ; mais qu’est-ce que ça veut dire au
juste ?
Ceci nous donne un prétexte pour une première application pratique des notions de module (que nous
avons introduits en fin de semaine 2) et de classe (que nous allons voir en fin de semaine).
De même que le langage nous propose les types builtin int et str, le module pathlib nous expose un
type (on dira plutôt une classe) qui s’appelle Path, que nous allons importer comme ceci :
Pour ça nous créons un objet qui est une instance de la classe Path, comme ceci :
w3-s1-c2-utilitaires-sur-fichiers 6
MOOC Python 3 Semaine 3, Séquence 1
en ce sens que le type (int ou Path) se comporte comme une usine pour créer des objets du type en
question.
Quoi qu’il en soit, cet objet path offre un certain nombre de méthodes ; pour les voir puisque nous
sommes dans un notebook, je vous invite dans la cellule suivante à utiliser l’aide en ligne en appuyant
sur la touche ‘Tabulation’ après avoir ajouté un . comme si vous alliez envoyer une méthode à cet objet
[5]: PosixPath('fichier-temoin')
Ainsi par exemple on peut savoir si le fichier existe avec la méthode exists()
[6]: False
[8]: True
Métadonnées
Voici quelques exemples qui montrent comment accéder aux métadonnées de ce fichier :
[9]: # cette méthode retourne (en un seul appel système) les métadonnées agrégées
path.stat()
Pour ceux que ça intéresse, l’objet retourné par cette méthode stat est un namedtuple, que l’on va voir
très bientôt.
w3-s1-c2-utilitaires-sur-fichiers 7
MOOC Python 3 Semaine 3, Séquence 1
[10]: 11
[11]: 1701681927.0462885
[13]: '10:25'
Détruire un fichier
[14]: # je peux maintenant détruire le fichier
path.unlink()
no need to remove
[16]: False
[17]: 'fichier-temoin'
w3-s1-c2-utilitaires-sur-fichiers 8
MOOC Python 3 Semaine 3, Séquence 1
Recherche de fichiers
Maintenant je voudrais connaître la liste des fichiers de nom *.json dans le directory data.
La méthode la plus naturelle consiste à créer une instance de Path associée au directory lui-même :
Sur cet objet la méthode glob nous retourne un itérable qui contient ce qu’on veut :
data/cities_europe.json
data/marine-e2-ext.json
data/cities_france.json
data/cities_idf.json
data/marine-e1-abb.json
data/cities_world.json
data/marine-e1-ext.json
data/marine-e2-abb.json
Documentation complète
Voyez la documentation complète ici
[20]: type(path)
[20]: pathlib.PosixPath
qui n’est pas Path, mais en fait une sous-classe de Path qui est - sur la plateforme du MOOC au moins,
qui fonctionne sous linux - un objet de type PosixPath, qui est une sous-classe de Path, comme vous
pouvez le voir :
[21]: True
Ce qui fait que mécaniquement, path est bien une instance de Path
[22]: True
ce qui est heureux puisqu’on avait utilisé Path() pour construire l’objet path au départ :)
w3-s1-c3-format-json-et-autres 9
MOOC Python 3 Semaine 3, Séquence 1
3.3 w3-s1-c3-format-json-et-autres
Le problème
Les données dans un programme Python sont stockées en mémoire (la RAM), sous une forme propice
aux calculs. Par exemple un petit entier est fréquemment stocké en binaire dans un mot de 64 bits, qui
est prêt à être soumis au processeur pour faire une opération arithmétique.
Ce format ne se prête pas forcément toujours à être transposé tel quel lorsqu’on doit écrire des données
sur un support plus pérenne, comme un disque dur, ou encore sur un réseau pour transmission distante
- ces deux supports étant à ce point de vue très voisins.
Ainsi par exemple il pourra être plus commode d’écrire notre entier sur disque, ou de le transmettre à
un programme distant, sous une forme décimale qui sera plus lisible, sachant que par ailleurs toutes les
machines ne codent pas un entier de la même façon.
Il convient donc de faire de la traduction dans les deux sens entre représentations d’une part en mémoire,
et d’autre part sur disque ou sur réseau (à nouveau, on utilise en général les mêmes formats pour ces
deux usages).
Le format JSON
Le format sans aucun doute le plus populaire à l’heure actuelle est le format JSON pour JavaScript
Object Notation.
Sans trop nous attarder nous dirons que JSON est un encodage - en anglais marshalling - qui se prête
bien à la plupart des types de base que l’on trouve dans les langages modernes comme Python, Ruby ou
JavaScript.
La bibliothèque standard de Python contient le module json que nous illustrons très rapidement ici :
# et relire le résultat
with open("s1.json", encoding='utf-8') as json_input:
data2 = json.load(json_input)
Limitations de json
Certains types de base ne sont pas supportés par le format JSON (car ils ne sont pas natifs en JavaScript),
c’est le cas notamment pour :
w3-s1-c3-format-json-et-autres 10
MOOC Python 3 Semaine 3, Séquence 1
Malgré ces petites limitations, ce format est de plus en plus populaire, notamment parce qu’on peut
l’utiliser pour communiquer avec des applications Web écrites en JavaScript, et aussi parce qu’il est très
léger, et supporté par de nombreux langages.
Le format pickle
Le format pickle remplit une fonctionnalité très voisine de JSON, mais est spécifique à Python. C’est
pourquoi, malgré des limites un peu moins sévères, son usage tend à rester plutôt marginal pour l’échange
de données, on lui préfère en général le format JSON.
Par contre, pour la sauvegarde locale d’objets Python (pour, par exemple, faire des points de reprises
d’un programme), il est très utile. Il est implémenté dans le module pickle.
Le format XML
Vous avez aussi très probablement entendu parler de XML, qui est un format assez populaire également.
Cela dit, la puissance, et donc le coût, de XML et JSON ne sont pas du tout comparables, XML étant
beaucoup plus flexible mais au prix d’une complexité de mise en œuvre très supérieure.
Il existe plusieurs souches différentes de bibliothèques prenant en charge le format XML, qui sont intro-
duites ici.
3.4 w3-s1-c4-fichiers-systeme
Fichiers systèmes
w3-s1-c4-fichiers-systeme 11
MOOC Python 3 Semaine 3, Séquence 1
Introduction
Dans un ordinateur, le système d’exploitation (Windows, Linux, macOS, etc.) comprend un noyau
(kernel) qui est un logiciel qui a l’exclusivité pour interagir physiquement avec le matériel (processeur(s),
mémoire, disque(s), périphériques, etc.) ; il offre aux programmes utilisateur (userspace) des abstractions
pour interagir avec ce matériel.
La notion de fichier, telle qu’on l’a vue dans la vidéo, correspond à une de ces abstractions ; elle repose
principalement sur les quatre opérations élémentaires suivantes :
— open ;
— close ;
— read ;
— write.
Parmi les autres conventions d’interaction entre le système (pour être précis : le shell) et une application,
il y a les notions de :
Ceci est principalement pertinent dans le contexte d’un terminal. L’idée c’est que l’on a envie de pouvoir
rediriger les entrées-sorties d’un programme sans avoir à le modifier. De la sorte, on peut également
chaîner des traitements à l’aide de pipes, sans avoir besoin de sauver les résultats intermédiaires sur
disque.
Les deux fichiers en question sont ouverts par le shell, et passés à monprogramme - que celui-ci soit écrit
en C, en Python ou en Java - sous la forme des fichiers stdin et stdout respectivement, et donc déjà
ouverts.
Le module sys
L’interpréteur Python vous expose ces trois fichiers sous la forme d’attributs du module sys :
Dans le contexte du notebook vous pouvez constater que les deux flux de sortie sont implémentés comme
des classes spécifiques à IPython. Si vous exécutez ce code localement dans votre ordinateur vous allez
sans doute obtenir quelque chose comme :
On n’a pas extrêmement souvent besoin d’utiliser ces variables en règle générale, mais elles peuvent
s’avérer utiles dans des contextes spécifiques.
w3-s1-c4-fichiers-systeme 12
MOOC Python 3 Semaine 3, Séquence 2
Par exemple, l’instruction print écrit dans sys.stdout (c’est-à-dire la sortie standard). Et comme
sys.stdout est une variable (plus exactement stdout est un attribut dans le module référencé par la
variable sys) et qu’elle référence un objet fichier, on peut lui faire référencer un autre objet fichier et
ainsi rediriger depuis notre programme toutes les sorties, qui sinon iraient sur le terminal, vers un fichier
de notre choix :
# première redirection
sys.stdout = autre_stdout
print('dans le fichier')
sur le terminal
de nouveau sur le terminal
dans le fichier
3.5 w3-s2-c1-tuple-et-virgule
La construction de tuples
w3-s2-c1-tuple-et-virgule 13
MOOC Python 3 Semaine 3, Séquence 2
[2]: True
Comme on le voit :
— en réalité la parenthèse est parfois superflue ; mais il se trouve qu’elle est largement utilisée pour
améliorer la lisibilité des programmes, sauf dans le cas du tuple unpacking ; nous verrons aussi plus
bas qu’elle est parfois nécessaire selon l’endroit où le tuple apparaît dans le programme ;
— la dernière virgule est optionnelle aussi, c’est le cas pour les tuples à au moins 2 éléments - nous
verrons plus bas le cas des tuples à un seul élément.
L’avantage lorsqu’on choisit cette forme (avec parenthèses, et avec virgule terminale), c’est d’abord qu’il
n’est pas nécessaire de mettre un backslash à la fin de chaque ligne ; parce que l’on est à l’intérieur d’une
zone parenthésée, l’interpréteur Python “sait” que l’instruction n’est pas terminée et va se continuer sur
la ligne suivante.
Deuxièmement, si on doit ultérieurement ajouter ou enlever un élément dans le tuple, il suffira d’enlever
ou d’ajouter toute une ligne, sans avoir à s’occuper des virgules ; si on avait choisi de ne pas faire figurer
la virgule terminale, alors pour ajouter un élément dans le tuple après le dernier, il ne faut pas oublier
d’ajouter une virgule à la ligne précédente. Cette simplicité se répercute au niveau du gestionnaire de
code source, où les différences dans le code sont plus faciles à visualiser.
Signalons enfin que ceci n’est pas propre aux tuples. La virgule terminale est également optionnelle pour
les listes, ainsi d’ailleurs que pour tous les types Python où cela fait du sens, comme les dictionnaires et les
ensembles que nous verrons bientôt. Et dans tous les cas où on opte pour une présentation multi-lignes,
il est conseillé de faire figurer une virgule terminale.
Tuples à un élément
Pour revenir à présent sur le cas des tuples à un seul élément, c’est un cas particulier, parmi les quatre
syntaxes que l’on a vues ci-dessus, on obtiendrait dans ce cas :
w3-s2-c1-tuple-et-virgule 14
MOOC Python 3 Semaine 3, Séquence 2
[5]: type(simple2)
[5]: int
Les deux autres formes créent par contre toutes les deux un tuple à un élément comme on cherchait à le
faire :
[6]: type(simple3)
[6]: tuple
[7]: True
Pour conclure, disons donc qu’il est conseillé de toujours mentionner une virgule terminale lorsqu’on
construit des tuples.
[8]: x = (1,)
(1,) == x
[8]: True
# NOTE
# auto-exec-for-latex has skipped execution of this cell
Python lève une erreur de syntaxe ; encore une bonne raison pour utiliser les parenthèses.
Addition de tuples
Bien que le type tuple soit immuable, il est tout à fait légal d’additionner deux tuples, et l’addition va
produire un nouveau tuple :
addition (1, 2, 3, 4)
Ainsi on peut également utiliser l’opérateur += avec un tuple qui va créer, comme précédemment, un
nouvel objet tuple :
w3-s2-c1-tuple-et-virgule 15
MOOC Python 3 Semaine 3, Séquence 2
Une astuce utile consiste à penser aux fonctions de conversion, pour construire un tuple à partir de - par
exemple - une liste. Ainsi on peut faire par exemple ceci :
Ces variables en effet sont des variables “comme les autres”. Imaginez qu’on ait en fait deux tuples à
construire comme ci-dessus, voici ce qu’on obtiendrait si on n’avait pas pris cette précaution :
[13]: (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
# NOTE
# auto-exec-for-latex has skipped execution of this cell
w3-s2-c1-tuple-et-virgule 16
MOOC Python 3 Semaine 3, Séquence 2
Il y a une erreur parce que nous avons remplacé (ligne 2) la valeur de la variable tuple, qui au départ
référençait le type tuple (ou si on préfère la fonction de conversion), par un objet tuple. Ainsi en ligne 5,
lorsqu’on appelle à nouveau tuple, on essaie d’exécuter un objet qui n’est pas ‘appelable’ (not callable
en anglais).
D’un autre côté, l’erreur est relativement facile à trouver dans ce cas. En cherchant toutes les occurrences
de tuple dans notre propre code on voit assez vite le problème. De plus, je vous rappelle que votre éditeur
de texte doit faire de la coloration syntaxique, et que toutes les fonctions built-in (dont tuple et list
font partie) sont colorées spécifiquement (par exemple, en violet sous IDLE). En pratique, avec un bon
éditeur de texte et un peu d’expérience, cette erreur est très rare.
3.6 w3-s2-c2-sequence-unpacking
Sequence unpacking
Déjà rencontré
L’affectation dans Python peut concerner plusieurs variables à la fois. En fait nous en avons déjà vu un
exemple en Semaine 1, avec la fonction fibonacci dans laquelle il y avait ce fragment :
Nous allons dans ce complément décortiquer les mécanismes derrière cette phrase qui a probablement
excité votre curiosité. :)
Un exemple simple
Commençons par un exemple simple à base de tuple. Imaginons que l’on dispose d’un tuple couple dont
on sait qu’il a deux éléments :
On souhaite à présent extraire les deux valeurs, et les affecter à deux variables distinctes. Une solution
naïve consiste bien sûr à faire simplement :
Cela fonctionne naturellement très bien, mais n’est pas très pythonique - comme on dit ;) Vous devez
toujours garder en tête qu’il est rare en Python de manipuler des indices. Dès que vous voyez des indices
dans votre code, vous devez vous demander si votre code est pythonique.
w3-s2-c2-sequence-unpacking 17
MOOC Python 3 Semaine 3, Séquence 2
La logique ici consiste à dire : affecter les deux variables de sorte que le tuple (gauche, droite) soit
égal à couple. On voit ici la supériorité de cette notion d’unpacking sur la manipulation d’indices : vous
avez maintenant des variables qui expriment la nature de l’objet manipulé, votre code devient expressif,
c’est-à-dire auto-documenté.
Remarquons que les parenthèses ici sont optionnelles - comme lorsque l’on construit un tuple - et on peut
tout aussi bien écrire, et c’est le cas d’usage le plus fréquent d’omission des parenthèses pour le tuple :
Autres types
Cette technique fonctionne aussi bien avec d’autres types. Par exemple, on peut utiliser :
Et on n’est même pas obligés d’avoir le même type à gauche et à droite du signe =, comme ici :
La plupart du temps le terme de gauche est écrit comme un tuple. C’est pour cette raison que les deux
termes tuple unpacking et sequence unpacking sont en vigueur.
w3-s2-c2-sequence-unpacking 18
MOOC Python 3 Semaine 3, Séquence 2
[7]: a = 1
b = 2
a, b = b, a
print('a', a, 'b', b)
a 2 b 1
Extended unpacking
Le extended unpacking a été introduit en Python 3 ; commençons par en voir un exemple :
Comme vous le voyez, le mécanisme ici est une extension de sequence unpacking ; Python vous autorise
à mentionner une seule fois, parmi les variables qui apparaissent à gauche de l’affectation, une variable
précédée de *, ici *b.
Cette variable est interprétée comme une liste de longueur quelconque des éléments de reference. On
aurait donc aussi bien pu écrire :
a=0 b=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18] c=19
Ce trait peut s’avérer pratique, lorsque par exemple on s’intéresse seulement aux premiers éléments d’une
structure :
prenom=Jean nom=Dupont
w3-s2-c2-sequence-unpacking 19
MOOC Python 3 Semaine 3, Séquence 2
print(f"a = {a}")
a = 3
Attention toutefois, comme on le voit ici, Python n’impose pas que les différentes occurrences de a
correspondent à des valeurs identiques (en langage savant, on dirait que cela ne permet pas de faire
de l’unification). De manière beaucoup plus pragmatique, l’interpréteur se contente de faire comme s’il
faisait l’affectation plusieurs fois de gauche à droite, c’est-à-dire comme s’il faisait :
[12]: a = 1; a = 2; a = 3
Cette technique n’est utilisée en pratique que pour les parties de la structure dont on n’a que faire dans
le contexte. Dans ces cas-là, il arrive qu’on utilise le nom de variable _, dont on rappelle qu’il est légal,
ou tout autre nom comme ignored pour manifester le fait que cette partie de la structure ne sera pas
utilisée, par exemple :
_, milieu, _ = entree
print('milieu', milieu)
milieu 2
right 3
En profondeur
Le sequence unpacking ne se limite pas au premier niveau dans les structures, on peut extraire des
données plus profondément imbriquées dans la structure de départ ; par exemple avec en entrée la liste :
trois 3
trois 3
trois 3
w3-s2-c2-sequence-unpacking 20
MOOC Python 3 Semaine 3, Séquence 2
Affaire de goût évidemment. Mais n’oublions pas une des phrases du Zen de Python
Flat is better than nested, ce qui veut dire que ce n’est pas parce que vous pouvez faire des structures
imbriquées complexes que vous devez le faire. Bien souvent, cela rend la lecture et la maintenance du
code complexe, j’espère que l’exemple précédent vous en a convaincu.
[18]: [1, 2, [(3, 33, 'three', 'thirty-three')], [4, 44, ('forty', 'forty-four')]�
�]
Dans ce cas, la limitation d’avoir une seule variable de la forme *extended s’applique toujours, naturel-
lement, mais à chaque niveau dans l’imbrication, comme on le voit sur cet exemple.
3.7 w3-s2-c3-for-sur-plusieurs-variables
a=1 b=2
D’une façon analogue, il est possible de faire une boucle for qui itère sur une seule liste mais qui agit
sur plusieurs variables, comme ceci :
a=1 b=2
a=3 b=4
a=5 b=6
w3-s2-c3-for-sur-plusieurs-variables 21
MOOC Python 3 Semaine 3, Séquence 2
À chaque itération, on trouve dans entree un tuple (d’abord (1, 2), puis à l’itération suivante (3,
4), etc.) ; à ce stade les variables a et b vont être affectées à, respectivement, le premier et le deuxième
élément du tuple, exactement comme dans le sequence unpacking. Cette mécanique est massivement
utilisée en Python.
Imaginons qu’on dispose de deux listes de longueurs égales, dont on sait que les entrées correspondent
une à une, comme par exemple :
Afin d’écrire facilement un code qui “associe” les deux listes entre elles, Python fournit une fonction
built-in baptisée zip ; voyons ce qu’elle peut nous apporter sur cet exemple :
On le voit, on obtient en retour une liste composée de tuples. On peut à présent écrire une boucle for
comme ceci :
Qui est, nous semble-t-il, beaucoup plus lisible que ce que l’on serait amené à écrire avec des langages
plus traditionnels.
Remarque : lorsqu’on passe à zip des listes de tailles différentes, le résultat est tronqué, c’est l’entrée de
plus petite taille qui détermine la fin du parcours.
1 10
2 20
w3-s2-c3-for-sur-plusieurs-variables 22
MOOC Python 3 Semaine 3, Séquence 2
La fonction enumerate
Une autre fonction très utile permet d’itérer sur une liste avec l’indice dans la liste, il s’agit de enumerate :
0 Paris
1 Nice
2 Lyon
Cette forme est plus simple et plus lisible que les formes suivantes qui sont équivalentes, mais qui ne sont
pas pythoniques :
0 Paris
1 Nice
2 Lyon
0 Paris
1 Nice
2 Lyon
3.8 w3-s2-x1-comptage
Fichiers
— qui prenne en argument un nom de fichier d’entrée (on suppose qu’il existe) et un nom de fichier
de sortie (on suppose qu’on a le droit de l’écrire) ;
— le fichier d’entrée est supposé encodé en UTF-8 ;
— le fichier d’entrée est laissé intact ;
— pour chaque ligne en entrée, le fichier de sortie comporte une ligne qui donne le numéro de ligne,
le nombre de mots (séparés par des espaces), le nombre de caractères (y compris la fin de ligne),
et la ligne d’origine.
w3-s2-x1-comptage 23
MOOC Python 3 Semaine 3, Séquence 2
N’oubliez pas de vérifier que vous ajoutez bien les fins de ligne, car la vérification automatique est
pointilleuse (elle utilise l’opérateur ==), et rejettera votre code si vous ne produisez pas une sortie rigou-
reusement similaire à ce qui est attendu.
exo_comptage.correction(comptage)
# NOTE
# auto-exec-for-latex has skipped execution of this cell
La méthode debug applique votre fonction au premier fichier d’entrée, et affiche le résultat comme dans
l’exemple ci-dessus :
[3]: # debugging
exo_comptage.debug(comptage)
Pour les courageux, je vous donne également “Une charogne” en ISO-8859-15, qui contient le même texte
que “Une charogne”, mais encodé en Latin-9, connu aussi sous le nom ISO-8859-15.
Ce dernier fichier n’est pas à prendre en compte dans la version basique de l’exercice, mais vous pourrez
vous rendre compte par vous-mêmes, au cas où cela ne serait pas clair encore pour vous, qu’il n’est
pas facile d’écrire une fonction comptage qui devine l’encodage, c’est-à-dire qui fonctionne correctement
avec des entrées indifféremment en Unicode ou Latin, sans que cet encodage soit passé en paramètre à
comptage.
C’est d’ailleurs le propos de la bibliothèque chardet qui s’efforce de déterminer l’encodage de fichiers
d’entrée, sur la base de modèles statistiques.
3.9 w3-s2-x2-surgery
Sequence unpacking
w3-s2-x2-surgery 24
MOOC Python 3 Semaine 3, Séquence 4
# NOTE
# auto-exec-for-latex has skipped execution of this cell
3.10 w3-s4-c1-dictionnaires
Dictionnaires
Création en extension
On l’a vu, la méthode la plus directe pour créer un dictionnaire est en extension comme ceci :
Remarquons qu’on peut aussi utiliser cette autre forme d’appel à dict pour un résultat équivalent :
w3-s4-c1-dictionnaires 25
MOOC Python 3 Semaine 3, Séquence 4
Remarquez ci-dessus l’absence de quotes autour des clés comme marc. Il s’agit d’un cas particulier de
passage d’arguments que nous expliciterons plus longuement en fin de semaine 4.
Accès atomique
Pour accéder à la valeur associée à une clé, on utilise la notation à base de crochets [] :
Cette forme d’accès ne fonctionne que si la clé est effectivement présente dans le dictionnaire. Dans le
cas contraire, une exception KeyError est levée. Aussi si vous n’êtes pas sûr que la clé soit présente, vous
pouvez utiliser la méthode get qui accepte une valeur par défaut :
Le dictionnaire est un type mutable, et donc on peut modifier la valeur associée à une clé :
[6]: annuaire['eric'] = 39
print(annuaire)
[7]: annuaire['bob'] = 42
print(annuaire)
Enfin pour détruire une entrée, on peut utiliser l’instruction del comme ceci :
Pour savoir si une clé est présente ou non, il est conseillé d’utiliser l’opérateur d’appartenance in comme
ceci :
False
w3-s4-c1-dictionnaires 26
MOOC Python 3 Semaine 3, Séquence 4
alice, age 30
eric, age 39
bob, age 42
On remarque d’abord que les entrées sont listées dans le désordre, plus précisément, il n’y a pas de notion
d’ordre dans un dictionnaire ; ceci est dû à l’action de la fonction de hachage, que nous avons vue dans
la vidéo précédente.
alice
eric
bob
30
39
42
La fonction len
On peut comme d’habitude obtenir la taille d’un dictionnaire avec la fonction len :
https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
w3-s4-c1-dictionnaires 27
MOOC Python 3 Semaine 3, Séquence 4
depuis cette version un dictionnaire est ordonné ; cela signifie qu’il se souvient de l’ordre dans lequel les
éléments ont été insérés, et c’est dans cet ordre que l’on itère sur le dictionnaire.
a 1
b 2
c 3
d 4
Je vous signale à toutes fins utiles, dans le module collections la classe OrderedDict, qui est une
personnalisation (une sous-classe) du type dict, date de l’époque (jusque 3.7 donc) où le dictionnaire natif
n’avait pas cette bonne propriété, et qui reste disponible pour des raisons de compatibilité ascendante.
Avec un dictionnaire de base, cela peut vous amener à écrire un code qui ressemble à ceci :
for x, y in tuples:
if x not in resultat:
resultat[x] = []
resultat[x].append(y)
1 [2, 3]
2 [1, 4]
Cela fonctionne, mais n’est pas très élégant. Pour simplifier ce type de traitement, vous pouvez utiliser
defaultdict, une sous-classe de dict dans le module collections :
w3-s4-c1-dictionnaires 28
MOOC Python 3 Semaine 3, Séquence 4
1 [2, 3]
2 [1, 4]
Cela fonctionne aussi avec le type int, lorsque vous voulez par exemple compter des occurrences :
for c in phrase:
compteurs[c] += 1
sorted(compteurs.items())
Signalons enfin une fonctionnalité un peu analogue, quoiqu’un peu moins élégante à mon humble avis,
mais qui est présente avec les dictionnaires dict standard. Il s’agit de la méthode setdefault qui permet,
en un seul appel, de retourner la valeur associée à une clé et de créer cette clé au besoin, c’est-à-dire si
elle n’est pas encore présente :
[21]: # avant
annuaire
w3-s4-c1-dictionnaires 29
MOOC Python 3 Semaine 3, Séquence 4
[22]: # ceci sera sans effet car eric est déjà présent
annuaire.setdefault('eric', 50)
[22]: 70
[23]: 50
[24]: {'alice': 30, 'eric': 70, 'bob': 42, 'jean': 25, 'inconnu': 50}
Notez bien que setdefault peut éventuellement créer une entrée mais ne modifie jamais la valeur associée
à une clé déjà présente dans le dictionnaire, comme le nom le suggère d’ailleurs.
Comme ce sont des itérables, on peut naturellement faire un for avec, on l’a vu :
a
b
True
False
w3-s4-c1-dictionnaires 30
MOOC Python 3 Semaine 3, Séquence 4
[29]: False
Ce qui signifie qu’on n’a pas alloué de mémoire pour stocker toutes les clés, mais seulement un objet qui
ne prend pas de place, ni de temps à construire :
83 ns ± 4.36 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each)
[32]: # on répète ici car timeit travaille dans un espace qui lui est propre
# et donc on n'a pas défini big_keys pour notre interpréteur
big_keys = big_dict.keys()
[33]: %%timeit -n 20
# si on devait vraiment construire la liste ce serait beaucoup plus long
big_lkeys = list(big_keys)
16.6 ms ± 788 µs per loop (mean ± std. dev. of 7 runs, 20 loops each)
a
b
w3-s4-c1-dictionnaires 31
MOOC Python 3 Semaine 3, Séquence 4
b
c
Reportez vous à la section sur les vues de dictionnaires pour plus de détails.
3.11 w3-s4-c2-cles-immuables
Clés immuables
C’est-à-dire que, pour simplifier, on localise la présence d’une clé en calculant d’abord
f (cl ) = hash
puis on poursuit la recherche en utilisant hash comme indice dans le tableau contenant les couples (clé,
valeur).
On le rappelle, c’est cette astuce qui permet de réaliser les opérations sur les dictionnaires en temps
constant - c’est-à-dire indépendamment du nombre d’éléments.
Cependant, pour que ce mécanisme fonctionne, il est indispensable que la valeur de la clé reste inchangée
pendant la durée de vie du dictionnaire. Sinon, bien entendu, on pourrait avoir le scénario suivant :
et donc, avec ces hypothèses, on n’a plus la garantie de bon fonctionnement de la logique.
Type Mutable ?
int, float immuable
complex,bool immuable
str immuable
list mutable
dict mutable
set mutable
frozenset immuable
Le point important ici, est qu’il ne suffit pas, pour une clé, d’être de type immuable.
w3-s4-c2-cles-immuables 32
MOOC Python 3 Semaine 3, Séquence 4
[1]: d = {}
Cet objet est non seulement de type immuable, mais tous ses composants et sous-composants sont
immuables, on peut donc l’utiliser comme clé dans le dictionnaire :
Si à présent on essaie d’utiliser comme clé un tuple qui contient une liste :
Il se trouve que cette clé, bien que de type immuable, peut être indirectement modifiée puisque :
[5]: mauvaise_cle[1].append(3)
print(mauvaise_cle)
Et c’est pourquoi on ne peut pas utiliser cet objet comme clé dans le dictionnaire :
# NOTE
# auto-exec-for-latex has skipped execution of this cell
Pour conclure, il faut retenir qu’un objet n’est éligible pour être utilisé comme clé que s’il est composé
de types immuables de haut en bas de la structure de données.
La raison d’être principale du type tuple, que nous avons vu la semaine passée, et du type frozenset,
que nous verrons très prochainement, est précisément de construire de tels objets globalement immuables.
Épilogue
Tout ceci est valable pour les types built-in. Nous verrons que pour les types définis par l’utilisateur -
les classes donc - que nous effleurons à la fin de cette semaine et que nous étudions plus en profondeur
en semaine 6, c’est un autre mécanisme qui est utilisé pour calculer la clé de hachage d’une instance de
classe.
3.12 w3-s4-c3-record-et-dictionnaire
w3-s4-c3-record-et-dictionnaire 33
MOOC Python 3 Semaine 3, Séquence 4
Imaginons qu’on veuille manipuler un ensemble de données concernant des personnes ; chaque personne
est supposée avoir un nom, un âge et une adresse mail.
Il est possible, et assez fréquent, d’utiliser le dictionnaire comme support pour modéliser ces données
comme ceci :
[1]: personnes = [
{'nom': 'Pierre', 'age': 25, 'email': '[email protected]'},
{'nom': 'Paul', 'age': 18, 'email': '[email protected]'},
{'nom': 'Jacques', 'age': 52, 'email': '[email protected]'},
]
Bon, très bien, nous avons nos données, il est facile de les utiliser.
[2]: personnes[0]['age'] += 1
==========
nom -> Pierre
age -> 26
email -> [email protected]
==========
nom -> Paul
age -> 18
email -> [email protected]
==========
nom -> Jacques
age -> 52
email -> [email protected]
Si on imagine qu’on a commencé par lire ces données séquentiellement dans un fichier, et qu’on a calculé
l’objet personnes comme la liste qu’on a vue ci-dessus, alors il est possible de construire un index de
ces dictionnaires, (un dictionnaire de dictionnaires, donc).
w3-s4-c3-record-et-dictionnaire 34
MOOC Python 3 Semaine 3, Séquence 4
Attardons-nous un tout petit peu ; nous avons construit un dictionnaire par compréhension, en créant
autant d’entrées que de personnes. Nous aborderons en détail la notion de compréhension de sets et de
dictionnaires en semaine 5, donc si cette notation vous paraît étrange pour le moment, pas d’inquiétude.
Nom : Pierre -> enregistrement : {'nom': 'Pierre', 'age': 26, 'email': 'pie�
�[email protected]'}
Nom : Paul -> enregistrement : {'nom': 'Paul', 'age': 18, 'email': 'paul@ex�
�ample.com'}
Nom : Jacques -> enregistrement : {'nom': 'Jacques', 'age': 52, 'email': 'j�
�[email protected]'}
Dans cet exemple, le premier niveau de dictionnaire permet de trouver rapidement un objet à partir d’un
nom ; dans le second niveau au contraire on utilise le dictionnaire pour implémenter un enregistrement,
à la façon d’un struct en C.
Techniques similaires
Notons enfin qu’il existe aussi, en Python, un autre mécanisme qui peut être utilisé pour gérer ce genre
d’objets composites, ce sont les classes que nous verrons en semaine 6, et qui permettent de définir
de nouveaux types plutôt que, comme nous l’avons fait ici, d’utiliser un type prédéfini. Dans ce sens,
l’utilisation d’une classe permet davantage de souplesse, au prix de davantage d’effort.
Pour commencer je définis la classe Personne, qui va me servir à modéliser chaque personne :
w3-s4-c3-record-et-dictionnaire 35
MOOC Python 3 Semaine 3, Séquence 4
def __repr__(self):
return f"{self.nom} ({self.age} ans) sur {self.email}"
[8]: personnes2 = [
Personne('Pierre', 25, '[email protected]'),
Personne('Paul', 18, '[email protected]'),
Personne('Jacques', 52, '[email protected]'),
]
[9]: personnes2[0]
Je peux indexer tout ceci comme tout à l’heure, si j’ai besoin d’un accès rapide :
Le principe ici est exactement identique à ce qu’on a fait avec le dictionnaire de dictionnaires, mais on
a construit un dictionnaire d’instances.
Et de cette façon :
[11]: print(index2['Pierre'])
3.13 w3-s4-x1-graph-dict
Dictionnaires et listes
s1 10 s2
s2 12 s3
s3 25 s1
s1 14 s3
qui signifierait :
w3-s4-x1-graph-dict 36
MOOC Python 3 Semaine 3, Séquence 4
— etc…
On vous demande d’écrire une fonction qui lit un tel fichier texte, et construit (et retourne) un dictionnaire
Python qui représente ce graphe.
— de modéliser le graphe comme un dictionnaire indexé sur les (noms de) sommets ;
— et chaque valeur est une liste de tuples de la forme (suivant, longueur), dans l’ordre d’apparition
dans le fichier d’entrée.
[2]: # voici ce qu'on obtiendrait par exemple avec les données ci-dessus
from corrections.exo_graph_dict import exo_graph_dict
exo_graph_dict.example()
Notes
— Vous remarquerez que l’exemple ci-dessus retourne un dictionnaire standard ; une solution qui
utiliserait defaultdict est acceptable également ;
— Notez bien également que dans le résultat, la longueur d’un arc est attendue comme un int.
# à vous de jouer
def graph_dict(filename):
"votre code"
[ ]: exo_graph_dict.correction(graph_dict)
# NOTE
# auto-exec-for-latex has skipped execution of this cell
3.14 w3-s4-x2-marine-dict
3.14.1 Exercices
Cet exercice vient en deux versions, une de niveau basique et une de niveau intermédiaire.
La version basique est une application de la technique d’indexation que l’on a vue dans le complément
“Gérer des enregistrements”. On peut très bien faire les deux versions dans l’ordre, une fois qu’on a fait
la version basique on est en principe un peu plus avancé pour aborder la version intermédiaire.
Contexte
Nous allons commencer à utiliser des données un peu plus réalistes. Il s’agit de données obtenues auprès
de MarineTraffic - et légèrement simplifiées pour les besoins de l’exercice. Ce site expose les coordonnées
géographiques de bateaux observées en mer au travers d’un réseau de collecte de type crowdsourcing.
De manière à optimiser le volume de données à transférer, l’API de MarineTraffic offre deux modes pour
obtenir les données :
w3-s4-x2-marine-dict 37
MOOC Python 3 Semaine 3, Séquence 4
— mode étendu : chaque mesure (bateau x position x temps) est accompagnée de tous les détails du
bateau (id, nom, pays de rattachement, etc.) ;
— mode abrégé : chaque mesure est uniquement attachée à l’id du bateau.
En effet, chaque bateau possède un identifiant unique qui est un entier, que l’on note id.
sachant que les entrées après le code pays dans le format étendu ne nous intéressent pas pour cet exercice.
[2]: # une entrée étendue est une liste qui ressemble à ceci
sample_extended_entry = extended[3]
print(sample_extended_entry)
But de l’exercice
On vous demande d’écrire une fonction index qui calcule, à partir de la liste des données étendues, un
dictionnaire qui est :
w3-s4-x2-marine-dict 38
MOOC Python 3 Semaine 3, Séquence 4
Et si :
{
id1 -> [ id_bateau1, latitude, ... ],
id2 ...
}
Bref, on veut pouvoir retrouver les différents éléments de la liste extended par accès direct, en ne faisant
qu’une seule recherche dans l’index.
==== clé
992271012
==== valeur
[992271012, 47.64744, -3.509282, '2013-10-08T21:50:00', 'PENMEN', 'FR', '',�
� '']
Remarquez ci-dessus l’utilisation d’un utilitaire parfois pratique : le module pprint pour pretty-printer.
Votre code
[6]: def index(extended):
"<votre_code>"
Validation
[ ]: exo_index.correction(index, abbreviated)
# NOTE
# auto-exec-for-latex has skipped execution of this cell
w3-s4-x2-marine-dict 39
MOOC Python 3 Semaine 3, Séquence 4
Vous remarquerez d’ailleurs que la seule chose que l’on utilise dans cet exercice, c’est que l’id des bateaux
arrive en première position (dans la liste qui matérialise le bateau), aussi votre code doit marcher à
l’identique avec les bateaux étendus :
[ ]: exo_index.correction(index, extended)
# NOTE
# auto-exec-for-latex has skipped execution of this cell
But de l’exercice
On vous demande d’écrire une fonction merge qui fasse une consolidation des données, de façon à obtenir
en sortie un dictionnaire :
dans lequel les deux objets position sont tous les deux des tuples de la forme :
(992271012,
['PENMEN',
'FR',
(47.64744, -3.509282, '2013-10-08T21:50:00'),
(47.64748, -3.509307, '2013-10-08T22:56:00')])
Votre code
[9]: def merge(extended, abbreviated):
"votre code"
Validation
[ ]: exo_merge.correction(merge, extended, abbreviated)
# NOTE
# auto-exec-for-latex has skipped execution of this cell
w3-s4-x2-marine-dict 40
MOOC Python 3 Semaine 3, Séquence 5
Nous avons beaucoup simplifié les données d’entrée pour vous permettre une mise au point plus facile.
Si vous voulez vous amuser à charger des données un peu plus significatives, sachez que :
Une fois que vous avez un code qui fonctionne vous pouvez le lancer sur ces données plus copieuses en
faisant :
# NOTE
# auto-exec-for-latex has skipped execution of this cell
3.15 w3-s5-c1-ensembles
Ensembles
Création en extension
On crée un ensemble avec les accolades, comme les dictionnaires, mais sans utiliser le caractère :, et cela
donne par exemple :
w3-s5-c1-ensembles 41
MOOC Python 3 Semaine 3, Séquence 5
[3]: type({})
[3]: dict
Ceci est lié à des raisons historiques, les ensembles n’ayant fait leur apparition que tardivement dans le
langage en tant que citoyen de première classe.
<class 'set'>
Ou également, moins élégant mais que l’on trouve parfois dans du vieux code :
<class 'set'>
Le type set étant lui-même mutable, on ne peut pas créer un ensemble d’ensembles :
w3-s5-c1-ensembles 42
MOOC Python 3 Semaine 3, Séquence 5
Il n’existe pas de raccourci syntaxique comme les {} pour créer un ensemble immuable, qui doit être
créé avec la fonction frozenset. Toutes les opérations documentées dans ce notebook, et qui n’ont pas
besoin de modifier l’ensemble, sont disponibles sur un frozenset.
Parmi les fonctions exclues sur un frozenset, on peut citer : update, pop, clear, remove ou discard.
Opérations simples
Test d’appartenance
[7]: (1, 2, 3) in heteroclite
[7]: True
Cardinal
[8]: len(heteroclite)
[8]: 4
Manipulations
[9]: ensemble = {1, 2, 1}
ensemble
[9]: {1, 2}
[10]: set()
[11]: {1}
w3-s5-c1-ensembles 43
MOOC Python 3 Semaine 3, Séquence 5
ensemble
[15]: {1, 2}
La capture d’exception avec try et except sert à capturer une erreur d’exécution du programme (que l’on
appelle exception) pour continuer le programme. Le but de cet exemple est simplement de montrer (d’une
manière plus élégante que de voir simplement le programme planter avec une exception non capturée)
que l’expression ensemble.remove('foo') génère une exception. Si ce concept vous paraît obscur, pas
d’inquiétude, nous l’aborderons cette semaine et nous y reviendrons en détail en semaine 6.
element 1
element 2
et bien sûr maintenant l'ensemble est vide set()
A2 {0, 2, 4, 6}
A3 {0, 3, 6}
w3-s5-c1-ensembles 44
MOOC Python 3 Semaine 3, Séquence 5
N’oubliez pas que les ensembles ne sont pas ordonnés (contrairement aux dictionnaires que, depuis
Python-3.7, l’on parcourt dans l’ordre de création des clés)
Remarques :
— les notations des opérateurs sur les ensembles rappellent les opérateurs “bit-à-bit” sur les entiers ;
— ces opérateurs sont également disponibles sous la forme de méthodes.
Union
[19]: A2 | A3
[19]: {0, 2, 3, 4, 6}
Intersection
[20]: A2 & A3
[20]: {0, 6}
Différence
[21]: A2 - A3
[21]: {2, 4}
[22]: A3 - A2
[22]: {3}
Différence symétrique
On rappelle que A∆B = ( A − B) ∪ ( B − A)
[23]: A2 ^ A3
[23]: {2, 3, 4}
Comparaisons
Ici encore on se donne deux ensembles :
superset {0, 1, 2, 3}
subset {1, 3}
Égalité
[25]: heteroclite == heteroclite2
w3-s5-c1-ensembles 45
MOOC Python 3 Semaine 3, Séquence 5
[25]: True
Inclusion
[26]: subset <= superset
[26]: True
[27]: True
[28]: False
Ensembles disjoints
[29]: heteroclite.isdisjoint(A3)
[29]: True
3.16 w3-s5-x1-read-set
Ensembles
4615
12
9228
6158
12
read_set va prendre en argument un nom de fichier (vous pouvez supposer qu’il existe), enlever les
espaces éventuelles au début et à la fin de chaque ligne, et construire un ensemble de toutes les lignes ;
par exemple :
w3-s5-x1-read-set 46
MOOC Python 3 Semaine 3, Séquence 5
# NOTE
# auto-exec-for-latex has skipped execution of this cell
Ceci étant acquis, on veut écrire une deuxième fonction search_in_set qui prend en argument deux
fichiers :
Pour cela search_in_set doit retourner une liste contenant, pour chaque ligne du fichier filename, et
dans cet ordre, un tuple avec :
Par exemple :
4615
12
9228
6158
12
2048
8192
9228
2049
3
4
2053
2054
6158
4099
8
12
w3-s5-x1-read-set 47
MOOC Python 3 Semaine 3, Séquence 5
[7]: exo_search_in_set.example()
[8]: # à vous
def search_in_set(filename_reference, filename):
"votre code"
[ ]: # vérifiez
exo_search_in_set.correction(search_in_set)
# NOTE
# auto-exec-for-latex has skipped execution of this cell
3.17 w3-s5-x2-marine-set
[2]: print(extended[0])
[3]: print(abbreviated[0])
But de l’exercice
Notez bien une différence importante avec l’exercice précédent : cette fois il n’y a plus correspondance
entre les bateaux rapportés dans les données étendues et abrégées.
Le but de l’exercice est précisément d’étudier la différence, et pour cela on vous demande d’écrire une
fonction
w3-s5-x2-marine-set 48
MOOC Python 3 Semaine 3, Séquence 5
diff(extended, abbreviated)
— l’ensemble (set) des noms des bateaux présents dans extended mais pas dans abbreviated ;
— l’ensemble des noms des bateaux présents dans extended et dans abbreviated ;
— l’ensemble des id des bateaux présents dans abbreviated mais pas dans extended (par construc-
tion, les données ne nous permettent pas d’obtenir les noms de ces bateaux).
Votre code
# NOTE:
# auto-exec-for-latex has used hidden code instead
Validation
# NOTE
# auto-exec-for-latex has skipped execution of this cell
w3-s5-x2-marine-set 49
MOOC Python 3 Semaine 3, Séquence 6
— data/marine-e2-ext.json
— data/marine-e2-abb.json
— on a supprimé les entrées correspondant à des bateaux différents mais de même nom ; cette situation
peut arriver dans la réalité (c’est pourquoi d’ailleurs les bateaux ont un id) mais ici ce n’est pas le
cas ;
— il se peut par contre qu’un même bateau fasse l’objet de plusieurs mesures dans extended et/ou
dans abbreviated.
3.18 w3-s6-c1-try-finally
Sachez que l’on peut aussi utiliser - après toutes les clauses except :
— une clause else, qui va être exécutée si aucune exception n’est attrapée ;
— et/ou une clause finally qui sera alors exécutée quoi qu’il arrive.
w3-s6-c1-try-finally 50
MOOC Python 3 Semaine 3, Séquence 6
finally
C’est sans doute finally qui est la plus utile de ces deux clauses, car elle permet de faire un nettoyage
dans tous les cas de figure - de ce point de vue, cela rappelle un peu les context managers.
Et par exemple, comme avec les context managers, une fonction peut faire des choses même après un
return.
[2]: 1.0
[3]: 'zero-divide'
else
La logique ici est assez similaire, sauf que le code du else n’est exécuté que dans le cas où aucune
exception n’est attrapée.
En première approximation, on pourrait penser que c’est équivalent de mettre du code dans la clause
else ou à la fin de la clause try. En fait il y a une différence subtile :
The use of the else clause is better than adding additional code to the try clause because
it avoids accidentally catching an exception that wasn’t raised by the code being protected
by the try … except statement.
Dit autrement, si le code dans la clause else lève une exception, celle-ci ne sera pas attrapée par le try
courant, et sera donc propagée.
Voici un exemple rapide, en pratique on rencontre assez peu souvent une clause else dans un try :
w3-s6-c1-try-finally 51
MOOC Python 3 Semaine 3, Séquence 7
except ZeroDivisionError as e:
print(f"OOPS, {type(e)}, {e}")
else:
print("on passe ici seulement avec un nombre non nul")
return 'something else'
Remarquez que else ne présente pas cette particularité de “traverser” le return, que l’on a vue avec
finally :
[8]: 1.0
[9]: 'zero-divide'
w3-s7-c1-operateur-is-et-fonction-id 52
MOOC Python 3 Semaine 3, Séquence 7
3.19 w3-s7-c1-operateur-is-et-fonction-id
L’opérateur is
Les opérateurs is et ==
— nous avons déjà parlé de l’opérateur == qui compare la valeur de deux objets ;
— python fournit aussi un opérateur is qui permet de savoir si deux valeurs correspondent au même
objet en mémoire.
Scénario 1
[2]: # deux listes identiques
a = [1, 2]
b = [1, 2]
== True
is False
Scénario 2
[4]: # par contre ici il n'y a qu'une liste
a = [1, 2]
== True
is True
w3-s7-c1-operateur-is-et-fonction-id 53
MOOC Python 3 Semaine 3, Séquence 7
# NOTE
# auto-exec-for-latex has skipped execution of this cell
Scénario 2
[ ]: %%ipythontutor curInstr=1
# équivalent à la forme ci-dessus
# a = [1, 2]
# b = a
a = b = [1, 2]
# NOTE
# auto-exec-for-latex has skipped execution of this cell
if undef is None:
print('indéfini')
indéfini
Plutôt que :
indéfini
Qui se comporte de la même manière (à nouveau, parce qu’on compare avec None), mais est légèrement
moins lisible, et franchement moins pythonique. :)
Notez aussi et surtout que is est plus efficace que ==. En effet is peut être évalué en temps constant,
puisqu’il s’agit essentiellement de comparer les deux adresses. Alors que pour == il peut s’agir de parcourir
toute une structure de données possiblement très complexe.
[8]: id(True)
w3-s7-c1-operateur-is-et-fonction-id 54
MOOC Python 3 Semaine 3, Séquence 7
[8]: 4364021696
Comme vous vous en doutez, l’opérateur is peut être décrit formellement à partir de id comme ceci :
(a is b) ⇐⇒ (id(a) == id(b))
[9]: a = 3
b = 3
print('a', id(a), 'b', id(b))
a 4365730096 b 4365730096
Tiens, c’est curieux, nous avons ici deux objets, que l’on pourrait penser différents, mais en fait ce sont
les mêmes ; a et b désignent le même objet python, et on a :
[10]: a is b
[10]: True
Il se trouve que, dans le cas des petits entiers, python réalise une optimisation de l’utilisation de la
mémoire. Quel que soit le nombre de variables dont la valeur est 3, un seul objet correspondant à l’entier
3 est alloué et créé, pour éviter d’engorger la mémoire. On dit que l’entier 3 est implémenté comme un
singleton ; nous reverrons ceci en exercice.
On trouve cette optimisation avec quelques autres objets python, comme par exemple :
[11]: a = ""
b = ""
a is b
[11]: True
[12]: a = "foo"
b = "foo"
a is b
[12]: True
Conclusion cette optimisation ne touche aucun type mutable (heureusement) ; pour les types immuables,
il n’est pas extrêmement important de savoir en détail quels objets sont implémentés de la sorte.
Ce qui est par contre extrêmement important est de comprendre la différence entre is et ==, et de les
utiliser à bon escient au risque d’écrire du code fragile.
w3-s7-c1-operateur-is-et-fonction-id 55
MOOC Python 3 Semaine 3, Séquence 7
https://docs.python.org/3/reference/datamodel.html#objects-values-and-types
qui aborde également la notion de “garbage collection”, que nous n’aurons pas le temps d’approfondir
dans ce MOOC.
3.20 w3-s7-c2-references-circulaires
Nous allons maintenant construire un objet un peu abscons. Cet exemple précis n’a aucune utilité pra-
tique, mais permet de bien comprendre la logique du langage.
À présent nous allons remplacer le premier et seul élément de la liste par… la liste elle-même :
[[…]]
Pour essayer de décrire l’objet liste ainsi obtenu, on pourrait dire qu’il s’agit d’une liste de taille 1 et de
profondeur infinie, une sorte de fil infini en quelque sorte.
Naturellement, l’objet obtenu est difficile à imprimer de manière convaincante. Pour faire en sorte que
cet objet soit tout de même imprimable, et éviter une boucle infinie, python utilise l’ellipse ... pour
indiquer ce qu’on appelle une référence circulaire. Si on n’y prenait pas garde en effet, il faudrait écrire
[[[[ etc. ]]]] avec une infinité de crochets.
Voici la même séquence exécutée sous http://pythontutor.com ; il s’agit d’un site très utile pour com-
prendre comment python implémente les objets, les références et les partages.
Cliquez sur le bouton Next pour avancer dans l’exécution de la séquence. À la fin de la séquence vous
verrez - ce n’est pas forcément clair - la seule cellule de la liste à se référencer elle-même :
[ ]: %%ipythontutor height=230
infini_1 = [None]
infini_1[0] = infini_1
# NOTE
# auto-exec-for-latex has skipped execution of this cell
Toutes les fonctions de python ne sont pas aussi intelligentes que print. Bien qu’on puisse comparer
cette liste avec elle-même :
[4]: True
il n’en est pas de même si on la compare avec un objet analogue mais pas identique :
w3-s7-c2-references-circulaires 56
MOOC Python 3 Semaine 3, Séquence 7
[[…]]
# NOTE
# auto-exec-for-latex has skipped execution of this cell
[6]: collection_de_points = [
{'x': 10,'y': 20},
{'x': 30,'y': 50},
# imaginez plein de points
]
[{'x': 10, 'y': 20, 'points': […]}, {'x': 30, 'y': 50, 'points': […]}]
On voit à nouveau réapparaître les ellipses, qui indiquent que pour chaque point, le nouveau champ
points est un objet qui a déjà été imprimé.
Cette technique est cette fois très utile et très utilisée dans la pratique, dès lors qu’on a besoin de
naviguer de manière arbitraire dans une structure de données compliquée. Dans cet exemple, pas très
réaliste naturellement, on pourrait à présent accéder depuis un point à tous les autres points de la
collection dont il fait partie.
À nouveau il peut être intéressant de voir le comportement de cet exemple avec http://pythontutor.com
pour bien comprendre ce qui se passe, si cela ne vous semble pas clair à première vue :
[ ]: %%ipythontutor curInstr=7
points = [
{'x': 10,'y': 20},
{'x': 30,'y': 50},
]
# NOTE
# auto-exec-for-latex has skipped execution of this cell
w3-s7-c3-les-differentes-copies 57
MOOC Python 3 Semaine 3, Séquence 7
3.21 w3-s7-c3-les-differentes-copies
Le module copy
Pour réaliser une copie, la méthode la plus simple, en ceci qu’elle fonctionne avec tous les types de
manière identique, consiste à utiliser le module standard copy, et notamment :
Un exemple
Nous allons voir le résultat des deux formes de copie sur un même sujet de départ.
La copie superficielle / shallow copie / copy.copy N’oubliez pas de cliquer le bouton Next dans la
fenêtre pythontutor :
# NOTE
# auto-exec-for-latex has skipped execution of this cell
w3-s7-c3-les-differentes-copies 58
MOOC Python 3 Semaine 3, Séquence 7
On rappelle aussi que, la source étant une liste, on aurait pu aussi bien faire la copie superficielle avec
shallow2 = source[:]
La copie profonde / deep copie / copy.deepcopy Sur le même objet de départ, voici ce que fait la copie
profonde :
# NOTE
# auto-exec-for-latex has skipped execution of this cell
— les deux objets mutables accessibles via source, c’est-à-dire la liste source[0] et l’ensemble
source[1], ont été tous deux dupliqués ;
— le tuple correspondant à source[2] n’est pas dupliqué, mais comme il n’est pas mutable on ne
peut pas modifier la copie au travers de la source ;
— de manière générale, on a la bonne propriété que la source et sa copie ne partagent rien qui soit
modifiable ;
— et donc on ne peut pas modifier l’un au travers de l’autre.
On retrouve donc à nouveau l’optimisation qui est mise en place dans python pour implémenter les
types immuables comme des singletons lorsque c’est possible. Cela a été vu en détail dans le complément
consacré à l’opérateur is.
w3-s7-c3-les-differentes-copies 59
MOOC Python 3 Semaine 3, Séquence 7
On retrouve ici ce qu’on avait déjà remarqué sous pythontutor, à savoir que les trois derniers objets -
immuables - n’ont pas été dupliqués comme on aurait pu s’y attendre.
On modifie la source
Il doit être clair à présent que, précisément parce que deep_copy est une copie en profondeur, on peut
modifier source sans impacter du tout deep_copy.
S’agissant de shallow_copy, par contre, seuls les éléments de premier niveau ont été copiés. Aussi si
on fait une modification par exemple à l’intérieur de la liste qui est le premier fils de source, cela sera
répercuté dans shallow_copy :
w3-s7-c3-les-differentes-copies 60
MOOC Python 3 Semaine 3, Séquence 7
avant, source [[1, 2, 3], {1, 2, 3}, (1, 2, 3), '123', 123]
avant, shallow_copy [[1, 2, 3], {1, 2, 3}, (1, 2, 3), '123', 123]
après, source [[1, 2, 3, 4], {1, 2, 3}, (1, 2, 3), '123', 123]
après, shallow_copy [[1, 2, 3, 4], {1, 2, 3}, (1, 2, 3), '123', 123]
Si par contre on remplace complètement un élément de premier niveau dans la source, cela ne sera pas
répercuté dans la copie superficielle :
avant, source [[1, 2, 3, 4], {1, 2, 3}, (1, 2, 3), '123', 123]
avant, shallow_copy [[1, 2, 3, 4], {1, 2, 3}, (1, 2, 3), '123', 123]
après, source ['remplacement', {1, 2, 3}, (1, 2, 3), '123', 123]
après, shallow_copy [[1, 2, 3, 4], {1, 2, 3}, (1, 2, 3), '123', 123]
Copie et circularité
Le module copy est capable de copier - même en profondeur - des objets contenant des références
circulaires.
[10]: l = [None]
l[0] = l
l
[10]: [[…]]
[11]: copy.copy(l)
[11]: [[[…]]]
[12]: copy.deepcopy(l)
[12]: [[…]]
3.22 w3-s7-c4-instruction-del
L’instruction del
w3-s7-c4-instruction-del 61
MOOC Python 3 Semaine 3, Séquence 7
Pour l’illustrer, nous utilisons un bloc try … except … pour attraper le cas échéant l’exception
NameError, qui est produite lorsqu’on référence une variable qui n’est pas définie.
[2]: # on la définit
a = 10
a= 10
slice= [2, 5, 8]
w3-s7-c4-instruction-del 62
MOOC Python 3 Semaine 3, Séquence 7
Sur un dictionnaire
Avec del on peut enlever une clé, et donc la valeur correspondante, d’un dictionnaire :
print('l', l)
print('d', d)
l [0, 1, 3]
d {'foo': 'bar'}
3.23 w3-s7-c5-affectation-simultanee
Affectation simultanée
Dans ce complément nous allons voir une autre forme de l’affectation, qui consiste à affecter le même
objet à plusieurs variables. Commençons par un exemple simple :
w3-s7-c5-affectation-simultanee 63
MOOC Python 3 Semaine 3, Séquence 7
[2]: a = b = 1
print('a', a, 'b', b)
a 1 b 1
La raison pour laquelle nous abordons cette construction maintenant est qu’elle a une forte relation
avec les références partagées ; pour bien le voir, nous allons utiliser une valeur mutable comme valeur à
affecter :
Dès lors nous sommes dans le cas typique d’une référence partagée ; une modification de a va se répercuter
sur b puisque ces deux variables désignent le même objet :
[4]: a.append(1)
print('a', a, 'b', b)
a [1] b [1]
a [1] b []
On voit que dans ce cas chaque affectation crée une liste vide différente, et les deux variables ne partagent
plus de donnée.
D’une manière générale, utiliser l’affectation simultanée vers un objet mutable crée mécaniquement des
références partagées, aussi vérifiez bien dans ce cas que c’est votre intention.
3.24 w3-s7-c6-affectation-operateurs-2
Ces constructions en Python s’inspirent clairement de C, aussi dans l’esprit ces constructions devraient
fonctionner en modifiant l’objet référencé par la variable.
w3-s7-c6-affectation-operateurs-2 64
MOOC Python 3 Semaine 3, Séquence 7
Mais les types numériques en Python ne sont pas mutables, alors que les listes le sont. Du coup le
comportement de += est différent selon qu’on l’utilise sur un nombre ou sur une liste, ou plus généralement
selon qu’on l’invoque sur un type mutable ou non. Voyons cela sur des exemples très simples :
[1]: True
print(a)
print(b)
print(a is b)
4
3
False
[3]: True
[1]
[1]
True
Vous voyez donc que la sémantique de += (c’est bien entendu le cas pour toutes les autres formes
d’instructions qui combinent l’affectation avec un opérateur) est différente suivant que l’objet référencé
par le terme de gauche est mutable ou immuable.
Pour cette raison, c’est là une opinion personnelle, cette famille d’instructions n’est pas le trait le plus
réussi dans le langage, et je ne recommande pas de l’utiliser.
w3-s7-c6-affectation-operateurs-2 65
MOOC Python 3 Semaine 3, Séquence 8
x += y
était équivalent à :
x = x + y
Au vu de ce qui précède, on voit que ce n’est pas tout à fait exact, puisque :
a = []
print("avant", id(a))
a += [1]
print("après", id(a))
avant 4546173760
après 4546173760
a = []
print("avant", id(a))
a = a + [1]
print("après", id(a))
avant 4545878400
après 4546173760
Vous voyez donc que vis-à-vis des références partagées, ces deux façons de faire mènent à un résultat
différent.
3.25 w3-s8-x1-fifo
Classe
Cette classe s’appelle Fifo pour First In, First Out, c’est-à-dire que les éléments retournés par outgoing
le sont dans le même ordre où ils ont été ajoutés.
La méthode outgoing retourne None lorsqu’on l’appelle sur une pile vide.
w3-s8-x1-fifo 66
MOOC Python 3 Semaine 3, Séquence 8
[ ]: # et la vérifier ici
exo_fifo.correction(Fifo)
# NOTE
# auto-exec-for-latex has skipped execution of this cell
w3-s8-x1-fifo 67