Les fichiers¶
04-03-2023
Objectifs
savoir parcourir en lecture tout ou partir d’un fichier texte
savoir produire des fichiers textes
connaître les canaux standards
stdin
etstdout
prendre conscience des dangers des données lues
découvrir le traitement d’exception.
Pourquoi des fichiers ?¶
Chaque jour, nous consultons et créons de très nombreux fichiers :
consultation de bases de données,
photographies,
…
Les fichiers sont utilisés pour assurer la pesistance et le partage des données.
Lorsqu’avec votre appareil photo numérique vous prenez le portrait de votre grand-mère, le signal lumineux capté est transformé en signal numérique, puis sauvegardé dans un fichier, fichier que vous pouvez ensuite partager avec votre entourage en l’envoyant en pièce jointe d’un courrier électronique ou d’un SMS.
Différents types de fichiers¶
On distingue deux types de fichiers :
les fichiers textes
et les fichiers binaires.
Nous commençons par présenter ces deux types.
Fichiers textes¶
Les fichiers textes sont ceux contenant … du texte.
Ils sont utilisés dans de très nombreux contexte dont voici quelques uns :
fichiers textes bruts (plain text)
fichiers source de programmes
fichiers HTML, CSS
fichiers CSV (format tableur textuel)
La structure que partagent tous ces formats de fichiers textes est celle de ligne.
Une ligne est une chaîne de caractères terminée par un marqueur de fin de ligne.
Ce marqueur de fin de ligne peut différer d’un système d’exploitation à l’autre. Dans les systèmes de type Unix (GNU-Linux et Mac OSX par exemple), ce marqueur est par défaut constitué d’un caractère ASCII de code 10 (0x0A) appelé LINE FEED
. Dans les divers systèmes MS-Windows, ce marqueur de fin de ligne est constitué de deux caractères ASCII de code 13 (0x0D) appelé CARRIAGE RETURN
et 10 (0x0A) dans cet ordre.
Le principal outil informatique pour lire/produire un fichier texte est un éditeur de texte.
Fichiers binaires¶
On déclare binaire tout fichier qui n’est pas un fichier texte.
fichiers exécutables résultant de la compilation d’un fichier texte source
fichiers archives (formats ZIP, TGZ, …)
fichiers images (formats PNG, JPG, GIF, TIFF, …)
fichiers sons (formats WAV, OGG, FLAC, MP3, …)
fichiers videos (formats AVI, MPEG, …)
Contrairement aux fichiers textes pour lesquels la structure de ligne est une structure commune, il n’y a pas de structure commune à tous les fichiers binaires. Les outils informatiques pour lire/produire les fichiers binaires dépendent évidemment du format de ces fichiers.
Lire, écrire dans des fichiers en Python¶
Note
Dans cette partie nous nous limitons essentiellement aux fichiers textes.
Ouverture d’un canal¶
Avant de pouvoir lire les informations contenues dans un fichier, ou de pouvoir écrire des informations dans un fichier, il est nécessaire d’ouvrir un canal de communication entre le programme et le fichier.
L’ouverture d’un canal peut se faire
en mode lecture, et dans ce cas on ne peut que lire des informations contenues dans le fichier ;
en mode écriture, et dans ce cas il n’est possible que d’écrire dans le fichier ;
ou enfin en mode lecture et écriture, mode permettant à la fois la lecture et l’écriture d’informations dans le fichier.
En Python, c’est la fonction open
qui permet l’ouverture de canaux.
Ouverture en mode lecture¶
La fonction open
prend au moins deux chaînes de caractères en paramètre :
d’abord le nom du fichier à ouvrir ;
ensuite le mode d’ouverture.
Pour le mode d’ouverture en lecture, ce second paramètre est 'rt'
(r
pour read et t
pour text) ou plus simplement 'r'
(par défaut l’ouverture se fait sur des fichiers textes).
>>> entree = open('foo.txt','r')
Avertissement
Si le fichier qu’on veut ouvrir n’existe pas une exception
FileNotFoundError
est déclenchée.
>>> entree = open('nexiste.pas', 'r')
...
FileNotFoundError: [Errno 2] No such file or directory: 'nexiste.pas'
Ouverture en mode écriture¶
C’est la chaîne de caractères 'wt'
ou plus simplement 'w'
donnée en
second paramètre de la fonction open
qui permet d’ouvrir un canal de
communication en écriture vers un fichier texte.
>>> sortie = open('nouveau.txt', 'w')
Avertissement
L’ouverture en écriture d’un canal vers un fichier existant
entraîne la perte des données que contenait ce fichier.
L’ouverture en mode 'w'
est donc une opération potentiellement
dangereuse.
C’est pourquoi, Python propose un mode particulier d’ouverture de canal
en écriture, qui vérifie préalablement l’existence ou non du fichier
qu’on veut ouvrir. C’est le mode x
.
En supposant que le fichier existe.bien
existe bien, voici ce
qu’on obtient par une tentative d’ouverture en mode x
:
>>> sortie = open('existe.bien', 'x')
...
FileExistsError: [Errno 17] File exists: 'existe.bien'
Autres modes d’ouverture¶
En Python, il est possible d’ouvrir des canaux avec d’autres modes que les simples modes de lecture ou d’écriture :
le mode ajout (append) qui permet d’ouvrir un canal en écriture vers un fichier existant afin d’ajouter de nouvelles données. Dans le cas d’un fichier texte toute nouvelle opération d’écriture se fait à la suite du texte existant avant l’ouverture. Ce mode d’ouverture est obtenu en mettant le caractère
'a'
en deuxième paramètre de la fonctionopen
. Si on utilise ce mode sur un fichier non existant, un nouveau fichier est créé.le mode lecture et écriture qui permet d’ouvrir un canal à la fois en lecture et en écriture vers un fichier existant. Dans la fonction
open
s’obtient en ajoutant le caractère'+'
à la suite de l’un des caractères'r'
,'w'
,'x'
ou'a'
.
Type d’un canal ouvert sur un fichier texte¶
Quelque soit le mode d’ouverture d’un canal vers un fichier texte, son type
est _io.TextIOWrapper
.
>>> type(entree)
<class '_io.TextIOWrapper'>
Fermeture¶
Tout canal ouvert doit être fermé lorqu’on n’en a plus besoin.
Pour fermer un canal, on utilise la méthode close
.
>>> entree.close()
>>> sortie.close()
Avertissement
L’oubli de fermeture d’un canal ouvert en écriture peut entraîner la perte de données.
Forme syntaxique with
¶
Python offre une structure syntaxique permettant d’omettre la commande explicite de fermeture d’un canal (et ainsi l’oubli de cette commande).
Cette forme débute par le mot clé with
et utilise le mot clé as
:
# instr avant
with open(..., ...) as f:
# traitement sur f
# instr après
L’identificateur placé après le mot-clé as
est le nom donné au canal ouvert
par la fonction open
. La portée de cette variable est le bloc (indenté) qui suit le with
. À la fin de ce bloc, le canal est automatiquement fermé, sans qu’il ne soit nécessaire d’invoquer la méthode close
.
De cette façon, les lignes précédentes sont équivalentes aux suivantes :
# instr avant
f = open(..., ...)
# traitement sur f
f.close()
# instr après
Lecture de données¶
Dans cette partie on s’intéresse aux méthodes de lecture à travers un canal ouvert.
Dans les exemples illustrant ces méthodes, le fichier timoleon.txt
est supposé contenir les trois lignes suivantes (et ces trois lignes
uniquement) :
Timoleon est un homme politique grec
ayant vécu au IVème siècle av. JC.
Il est connu pour avoir recolonisé la Sicile.
Lecture d’une ligne¶
La méthode readline
que possèdent les canaux ouverts en lecture renvoie
la ligne sur laquelle pointe actuellement le canal.
>>> entree = open('timoleon.txt', 'r')
>>> entree.readline()
'Timoleon est un homme politique grec\n'
Sur cet exemple, la méthode readline
renvoie la première ligne du fichier
timoleon.txt
. Cette ligne est une chaîne de caractères qui contient
le marqueur de fin de ligne.
Un deuxième appel à la même méthode va nous donner la seconde ligne :
>>> entree.readline()
'ayant vécu au IVème siècle av. JC.\n'
et un troisième appel donne la troisième ligne :
>>> entree.readline()
'Il est connu pour avoir recolonisé la Sicile.\n'
Ces trois appels identiques produisant des résultats différents montrent qu’un canal possède un état qui change après chaque lecture. Cet état variable indique en permanence à quel endroit dans le fichier la lecture est arrivée.
Après ces trois lignes lues, l’état dans lequel se trouve le canal de lecture est
la fin du fichier. Que se passe-t-il si nous invoquons encore la méthode
readline
?
>>> entree.readline ()
''
Nous constatons qu’une fois arrivé en fin de fichier, readline
renvoie
une chaîne de caractères vide.
Avertissement
Il ne faut pas confondre la chaîne vide renvoyée par
readline
et une ligne vide contenue dans le fichier texte.
En effet, si l’état du canal pointe vers une ligne vide du fichier,
la méthode readline
renvoie une chaîne de caractères contenant
le seul marqueur de fin de ligne : '\n'
, chaîne qui n’est donc
pas vide.
Lorsque readline
renvoie une chaîne vide, c’est qu’on est arrivé
à la fin du fichier. C’est une caractéristique qu’il est possible
d’exploiter en programmation.
Fermons le canal avant de présenter la prochaine méthode.
>>> entree.close()
Lecture de toutes les lignes¶
La méthode readlines
donne la liste de toutes les lignes restant à lire.
Si le canal vient d’être ouvert, cela revient à lire toutes les lignes du
fichier.
>>> entree = open('timoleon.txt', 'r')
>>> entree.readlines()
['Timoleon est un homme politique grec\n',
'ayant vécu au IVème siècle av. JC.\n',
'Il est connu pour avoir recolonisé la Sicile.\n']
On obtient la liste de toutes les lignes contenues dans le fichier
timoleon.txt
. L’état du canal pointe alors vers la fin du fichier.
Fermons le canal avant de présenter la dernière méthode de lecture.
>>> entree.close()
La méthode read
¶
Terminons par une troisième méthode de lecture qui ne respecte pas la structure
en lignes des fichiers textes.
Cette méthode, nommée read
, peut s’employer avec ou sans paramètre.
Avec paramètre, il faut lui donner le nombre de caractères qu’on désire lire dans le fichier.
>>> entree = open('timoleon.txt', 'r')
>>> entree.read(3)
'Tim'
>>> entree.read(20)
'oleon est un homme p'
>>> entree.read(30)
'olitique grec\nayant vécu au IV'
>>> entree.read(0)
''
Sans paramètre, la chaîne de caractères renvoyée par read
est constituée
de la totalité des caractères contenus dans le fichier depuis l’état courant
du canal.
>>> entree.read()
'ème siècle av. JC.\nIl est connu pour avoir recolonisé la Sicile.\n'
Impossible de lire¶
Il est impossible de lire (quelle que soit la méthode) dans un fichier ouvert en écriture.
>>> sortie = open('nouveau.txt', 'w')
>>> sortie.readline()
...
UnsupportedOperation: not readable
Écriture de données¶
Dans cette partie on s’intéresse aux méthodes d’écriture dans un fichier texte.
Écrire des chaînes de caractères¶
Avec la méthode write
, on peut écrire n’importe quelle chaîne de caractères
dans un fichier.
>>> sortie = open('nouveau.txt', 'w')
>>> sortie.write('Timoleon est un homme politique')
21
>>> sortie.write(' grec\n' )
6
>>> sortie.close()
Comme cet exemple le montre, la méthode write
renvoie le nombre de caractères écrits.
Écrire des lignes¶
La méthode writelines
est une méthode analogue à readlines
: elle permet d’écrire
dans un fichier une liste de chaînes de caractères.
Si chacune de ces chaînes se termine par un marqueur de fin de ligne, le nombre de lignes
écrites dans le fichier est égal à la longueur de la liste.
>>> sortie = open('nouveau.txt', 'w')
>>> sortie.writelines(['Timoleon est un homme politique grec\n',
'ayant vécu au IVème siècle av. JC.\n',
'Il est connu pour avoir recolonisé la Sicile.\n'])
>>> sortie.close()
Impossible d’écrire¶
Il est impossible d’écrire (quelque soit la méthode) dans un fichier ouvert en lecture.
>>> entree = open('timoleon.txt', 'r')
>>> entree.write('')
...
UnsupportedOperation: not writable
Exemples¶
Parcours complet d’un fichier texte (ici
cigale.txt
) et impression de chacune des lignes sur la sortie standard.En voici une première version, avec lecture intégrale du fichier (méthode
readlines
puis parcours de la liste de lignes obtenue :with open('cigale.txt','r') as entree: lignes_lues = entree.readlines() for ligne in lignes_lues: print(ligne, end = '')
Cette version est envisageable pour des textes de taille n’excédant pas les capacités de mémoire de la machine sur laquelle elle est exécutée.
Note
On utilise ici le paramètre nommé
end
de la fonctionprint
auquel on donne la chaîne vide comme valeur, pour empêcher la fonctionprint
de faire un passage à la ligne. En effet, il faut se souvenir que toute ligne lue dans un fichier texte (par l’une ou l’autre des méthodesreadline
oureadlines
) contient le marqueur de fin de ligne (\n
).Voici une seconde version, qui procède par traitement immédiat d’une ligne qui vient d’être lue :
with open('cigale.txt', 'r') as entree: ligne = entree.readline() while ligne != '': print(ligne, end='') ligne = entree.readline()
Voici une troisième version, variante de la précédente, qui s’appuie sur le fait que les canaux vers des fichiers ouverts en lecture sont itérables :
with open('cigale.txt', 'r') as entree: for ligne in entree: print(ligne, end='')
Recopie d’un fichier texte dans un autre.
Première version en faisant une lecture intégrale du fichier avec la méthode
read
.with open('cigale.txt', 'r') as entree: tout_lu = entree.read() with open ('cigale2.txt', 'w') as sortie: sortie.write(tout_lu)
Deuxième version en faisant une lecture intégrale du fichier à copier avec la méthode
readlines
, et une recopie avec la méthodewritelines
.with open('cigale.txt', 'r') as entree: les_lignes = entree.readlines() with open ('cigale2.txt', 'w') as sortie: sortie.writelines(les_lignes)
Troisième version, en recopiant chaque ligne immédiatement après leur lecture.
with open('cigale.txt', 'r') as entree: with open('cigale2.txt', 'w') as sortie: ligne = entree.readline() sortie.write(ligne)
Note
Dans cette troisième version on aurait pu remplacer les deux dernières lignes par la seule ligne :
sortie.write(entree.readline())
Codage des fichiers textes¶

Les chaînes de caractères de Python3 sont des chaînes Unicode. Lorsqu’elles sont écrites dans un fichier, elles sont encodées. Et lorsqu’elles sont lues depuis un fichier, elles sont décodées.
Il existe plusieurs codages des caractères. Citons-en trois :
l”ISO-8859-15, parfois appelé aussi LATIN9, encodage couramment employé en Europe occidentale dans les années 1980-2000, mais qui ne permet pas de coder tous les caractères Unicode ;
le cp1252 ou
Windows-1252
, encodage utilisé par défaut sur le système d’exploitation Windows, qui étant une variante mineure de l’ISO-8859 ne permet pas non plus de coder tous les caractères Unicode ;l”UTF-8, encodage développé dans les années 1990, très utilisé actuellement, qui permet de coder tous les caractères Unicode.
Si on ne le précise pas, le codage employé par la fonction open
est celui par défaut de la
plateforme sur laquelle le programme s’exécute :
aujourd’hui, avec les systèmes GNU-Linux : UTF-8 ;
avec les systèmes Windows : cp1252.
La fonction open
possède un paramètre optionnel nommé encoding
qui permet de préciser le codage voulu.
Exemple¶
Considérons une même chaîne de caractères (dont l’auteur est Gilles Esposito-Farèse) écrite dans deux fichiers texte, mais avec des codages différents :
>>> texte = "Dès Noël où un zéphyr haï me vêt de glaçons würmiens je dîne d'exquis rôtis de bœuf au kir à l'aÿ d'âge mûr & cætera !"
>>> with open('texte_utf8.txt', 'w', encoding='utf_8') as sortie:
sortie.write(texte + '\n')
>>> with open('texte_iso8859_15.txt', 'w', encoding='iso8859_15') as sortie:
sortie.write(texte + '\n')
Maintenant lisons cette chaîne dans chacun des deux fichiers en employant le bon décodeur.
>>> with open('texte_utf8.txt', 'r', encoding='utf_8') as entree:
lu1 = entree.readline()
>>> print(lu1)
Dès Noël où un zéphyr haï me vêt de glaçons würmiens je dîne d'exquis rôtis de bœuf au kir à l'aÿ d'âge mûr & cætera !
>>> with open('texte_iso8859_15.txt', 'r', encoding='iso8859_15') as entree:
lu2 = entree.readline()
>>> print(lu2)
Dès Noël où un zéphyr haï me vêt de glaçons würmiens je dîne d'exquis rôtis de bœuf au kir à l'aÿ d'âge mûr & cætera !
>> lu1 == lu2
True
Tout va bien ! Mais si nous employons le mauvais décodeur, on peut obtenir des caractères bizarres :
>>> with open('texte_utf8.txt', 'r', encoding='iso8859_15') as entree:
lu3 = entree.readline ()
>>> print(lu3)
DÚs Noël où un zéphyr haï me vêt de glaçons wÃŒrmiens je dîne d'exquis rÃŽtis de bÅuf au kir à l'aÿ d'âge mûr & cÊtera !
ou même obtenir un déclenchement d’exception dû à une impossibilité de décoder :
>>> with open('texte_iso8859_15.txt', 'r', encoding='utf_8') as entree:
lu4 = entree.readline ()
...
UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe8 in position 1: invalid continuation byte
L’attribut encoding
¶
L’attribut encoding
donne le codage/décodage utilisé par un canal ouvert
vers un fichier texte.
>>> with open('texte_utf8.txt', 'r', encoding='utf_8') as entree:
print(entree.encoding)
utf_8
>>> with open('texte_utf8.txt', 'r', encoding='iso8859_15') as entree:
print(entree.encoding)
iso8859_15
Les canaux standards¶
Il existe trois canaux dits standards prédéfinis :
le canal standard de lecture :
stdin
;le canal standard d’écriture :
stdout
;et le canal standard d’erreur :
stderr
.
En Python, ces canaux sont des variables définies dans le module sys
.
Nous ne dirons quelques mots que pour les deux premiers.
Canal standard de lecture¶
La variable stdin
du module sys
représente un canal de lecture en mode texte.
L’encodage par défaut dépend du système sur lequel on l’utilise.
Ce canal est ouvert, la commande open
est donc inutile.
Voici ce que nous apprend l’interpréteur Python lorsqu’on l’interroge sur ce canal 1 :
>>> import sys
>>> sys.stdin
<_io.TextIOWrapper name='<stdin>' mode='r' encoding='UTF-8'>
Depuis quel fichier les opérations de lecture via ce canal seront-elles effectuées ? La réponse sur un ordinateur classique est (par défaut) le clavier.
Avec la méthode readline
, une fois la commande exécutée, l’interpréteur attend (patiemment)
qu’une ligne de texte soit tapée au clavier avec bien entendu son marqueur de fin de ligne
qui, au clavier, est la touche Entrée
.
>>> sys.stdin.readline()
Une ligne de texte
'Une ligne de texte\n'
Dans ce qui précède, la commande sys.stdin.readline()
attend de l’utilisateur qu’il tape une
ligne (ici Une ligne de texte
). Une fois cette ligne terminée, la commande renvoie la
chaîne de caractères lue au clavier, accompagnée de son marqueur de fin de ligne.
La méthode readlines
quant à elle est en mesure de lire un nombre quelconque de lignes.
Seule la fin de fichier marque la fin de la lecture. Comment est marquée la fin de fichier pour une
lecture de données au clavier ? Cela dépend du système sur lequel on travaille :
dans un système du type Unix : Ctrl + D
dans un système Windows : Ctrl + Z.
>>> sys.stdin.readlines()
une ligne
une autre ligne
et encore une autre.
['une ligne\n', 'une autre ligne\n', 'et encore une autre.\n']
C’est la même chose pour la méthode read
sauf qu’elle renvoie une chaîne de caractères au lieu
d’une liste de chaînes.
>>> sys.stdin.read()
une ligne
une autre ligne
et encore une autre.
'une ligne\nune autre ligne\net encore une autre.\n'
Canal standard d’écriture¶
La variable stdout
du module sys
représente un canal d’écriture en mode texte
qu’il est inutile d’ouvrir.
Voici ce que nous apprend l’interpréteur Python lorsqu’on l’interroge sur ce canal :
>>> sys.stdout
<_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'>
Sur un ordinateur classique, les opérations d’écriture se font (par défaut) sur l’écran.
Les méthodes write
et writelines
permettent donc d’effectuer des opérations d’écriture de chaînes ou de listes de chaînes.
>>> sys.stdout.write('Timoleon est un homme politique')
Timoleon est un homme politique21
>>> sys.stdout.write ('Timoleon est un homme politique\n')
Timoleon est un homme politique
21
>>> sys.stdout.writelines(['Timoleon', ' est\n', ' un', ' homme', ' politique\n'])
Timoleon est
un homme politique
La fonction input
¶
La fonction input
prédéfinie en Python permet de lire une chaîne de caractères tapée au clavier.
Elle peut événtuellement être utilisée avec une chaîne de caractères passée en paramètre qui
pourra jouer le rôle d’une invite à la saisie.
Voici comment on peut envisager de la programmer avec les deux canaux stdin
et stdout
:
def my_input(prompt=''):
sys.stdout.write(prompt)
return sys.stdin.readline()
En principe cette fonction contient tout ce qu’il faut pour jouer le même rôle que la fonction
input
: elle imprime le prompt sur la sortie standard, puis elle renvoie la ligne tapée par
l’utilisateur.
Malheureusement, à l’usage un phénomène troublant apparaît :
>>> my_input('Votre nom ? ')
Timoleon
Votre nom ? 'Timoleon\n'
Le prompt n’est imprimé seulement qu’après l’entrée de son nom par l’utilisateur !
Cela est dû au fait que le système n’effectue pas toujours immédiatement les opérations d’écritures :
il les temporise (buffer).
Si on veut forcer une opération d’écriture temporisée, on peut utiliser la méthode flush
.
Voici donc une nouvelle version de la fonction my_input
qui remédie au défaut constaté :
def my_input(prompt=''):
"""
:param prompt: (str) [optionnel, valeur par défaut: chaine vide] chaîne à imprimer avant lecture
:return: (str) chaîne lue depuis l'entrée standard
:effet de bord: interrompt le flux d'exécution pour lire une chaîne
de caractères depuis l'entrée standard.
"""
sys.stdout.write(prompt)
sys.stdout.flush()
return sys.stdin.readline().rstrip('\n')
>>> my_input ('Votre nom ? ')
Votre nom ? Timoleon
'Timoleon'
Remarque
Notez l’utilisation de la méthode rstrip
qui supprime d’une chaîne de caractères le caractère
passé en paramètre situé à droite. Ici c’est le marqueur de fin de ligne située à la fin, de la
chaîne renvoyée par readline
qui est supprimé.
Traitement des données lues¶
Si les opérations d’écriture ne posent en général pas trop de problèmes, il n’en va pas de même pour les opérations de lecture.
Lorsque dans un programme des données sont lues, c’est pour qu’elles soient l’objet d’un certain traitement. Quelle certitude pouvons nous avoir sur la conformité ou validité de ces données, surtout lorsqu’elles proviennent d’une saisie par un opérateur humain ?
Retour sur les exceptions¶
Certaines instructions, bien que syntaxiquement correctes, peuvent déclencher pendant leur exécution des erreurs, erreurs qui interrompent cette exécution en déclenchant ce qu’en programmation on nomme exception.
En voici un petit panorama :
>>> 1 // 0
Traceback (most recent call last):
...
ZeroDivisionError: integer division or modulo by zero
>>> l = [0, 1]
>>> l[2]
Traceback (most recent call last):
...
IndexError: list index out of range
>>> int ('timoleon')
Traceback (most recent call last):
...
ValueError: invalid literal for int() with base 10: 'timoleon'
>>> x = 0
>>> assert x != 0, 'x ne peut pas être nul'
Traceback (most recent call last):
...
AssertionError: x ne peut pas être nul
Le loup entre dans la bergerie¶
Prenons pour exemple, le cas de la saisie au clavier d’une date.
Concevons donc une fonction sans paramètre dont le travail consiste à lire une date
dans un format j/m/a
, composé de trois nombres désignant le jour, le mois et l’année,
séparés par des /
.
La fonction qui suit donne une réalisation possible d’une telle fonction. Elle consiste
à saisir les données à l’aide de la fonction
input
;à découper la réponse à l’aide de la méthode
split
;et enfin à construire un triplet d’entiers avec les trois composantes de la réponse (qui sont des chaînes de caractères).
def lire_date1():
"""
:return: (tuple) une date sous la forme d'un triplet
(j, m, a) de nombres entiers
:effet de bord: lecture d'une donnée sur l'entrée standard
:CU: validité des données lues
"""
reponse = input('j/m/a ? ')
reponse = reponse.split('/')
date = (int(reponse[0]), int(reponse[1]), int(reponse[2]))
return date
Cette fonction convient bien pour des données correctement saisies :
>>> lire_date1()
j/m/a ? 22/2/2016
(22, 2, 2016)
Mais, elle accepte des données incorrectes comme
>>> lire_date1()
j/m/a ? 2/22/2016
(2, 22, 2016)
et déclenche des exceptions pour des données au format incorrect
>>> lire_date1()
j/m/a ? 22/fevrier/2016
Traceback (most recent call last):
...
ValueError: invalid literal for int() with base 10: 'fevrier'
>>> lire_date1()
j/m/a ? 22-2-2016
Traceback (most recent call last):
...
ValueError: invalid literal for int() with base 10: '22-2-2016'
Le loup est entré dans la bergerie !
Le loup est l’utilisateur du programme. Le programmeur ne le connaît le plus souvent pas. Et cet utilisateur est très certainement un être humain qui peut commettre des erreurs comme celles mentionnées ci-dessus.
L’instruction try ... except
ou comment repousser le loup ?¶
L’instruction try: ... except...
permet au programmeur de programmer des situations
normales (partie try
) et prévoir des situations anormales ou exceptionnelles (partie
except
).
Une telle forme d’instruction est appelée traitement d’exceptions. En Python elle s’écrit toujours :
try:
# instructions du traitement normal
except <exception> :
# instructions du traitement exceptionnel
Elle commence par le mot-clé try
suivi des deux points (:
), suivie d’un bloc (indenté)
d’instructions à exécuter dans le cas où aucune exception n’est déclenchée. Elle est suivie par une
(ou plusieurs) partie(s) débutant par le mot-clé except
définissant les actions à réaliser dans
le cas où l’exception précisée à la suite du mot-clé except
est déclenchée.
En voici un exemple :
>>> x = 1
>>> try:
... x = x + 10 // 0
... except ZeroDivisionError:
... print('Tentative de division par zéro')
Tentative de division par zéro
Comme on le constate, la partie normale déclenche l’exception ZeroDivisionError
,
exception qui est traitée dans la partie qui suit par l’exécution d’une simple instruction
print
. La variable x
n’a pas pu être modifiée.
>>> x
1
Considérons ce second exemple :
>>> x = 1
>>> try:
... x = x + 10 // int('zero')
... except ZeroDivisionError:
... print('Tentative de division par zéro')
Traceback (most recent call last):
...
ValueError: invalid literal for int() with base 10: 'zero'
La fonction int
est appliquée à une chaîne de caractères qui ne correspond pas à l’écriture
décimale d’un nombre entier : elle déclenche donc une exception ValueError
. Celle-ci n’est
pas traitée puisque seule ZeroDivisionError
l’est. Voici donc un traitement de deux exceptions :
>>> x = 1
>>> try:
... x = x + 10 // int ('zero')
... except ZeroDivisionError:
... print('Tentative de division par zéro')
... except ValueError:
... print('Écriture du nombre incorrecte')
Écriture du nombre incorrecte
On peut si on veut rassembler les deux exceptions un seul cas :
>>> x = 1
>>> try:
... x = x + 10 // int('zero')
... except ZeroDivisionError, ValueError:
... print('Situation anormale')
Situation anormale
mais cela n’est pas toujours une très bonne idée, puisque le traitement à effectuer dans une situation anormale dépend souvent de l’exception déclenchée.
Vers une meilleure version de la fonction lire_date
¶
La version lire_date2
contrôle la saisie en refusant toute saisie ne comportant
pas deux caractères /
séparant les trois nombres. Une boucle infinie (while True
)
répète inlassablement la saisie tant qu’elle ne contient pas les deux séparateurs requis.
On sort de cette boucle infinie via l’instruction return
.
def lire_date2():
"""
:return: (tuple) une date sous la forme d'un triplet
(j, m, a) de nombres entiers
:effet de bord: lecture d'une donnée sur l'entrée standard
:CU: validité des données lues
"""
while True:
reponse = input('j/m/a ? ')
reponse = reponse.split('/')
try:
assert len(reponse) == 3, 'il faut 2 /'
date = (int(reponse[0]), int(reponse[1]), int(reponse[2]))
return date
except AssertionError:
print('il faut 2 / ')
Voici un appel à cette fonction dans lequel il faut trois tentatives à l’utilisateur pour
fournir une saisie avec deux /
:
>>> lire_date2()
j/m/a ? 29-2-2016
j/m/a ? 29/2-2016
j/m/a ? 29/2/2016
(29, 2, 2016)
La fonction int
étant assez souple avec les espaces entourant les nombres,
la saisie qui suit se passe très bien :
>>> lire_date2()
j/m/a ? 29 / 2 / 2016
(29, 2, 2016)
La fonction lire_date2
assure que la saisie des informations de l’utilisateur comprend bien
deux séparateurs /
. Mais elle ne contrôle absolument pas le contenu des trois champs séparés
qui pourraient très bien ne pas contenir de représentation de nombres entiers :
>>> lire_date2()
j/m/a ? a/b/c
Traceback (most recent call last):
...
ValueError: invalid literal for int() with base 10: 'a'
La fonction lire_date3
récupère l’exeption ValueError
:
def lire_date3():
"""
:return: (tuple) une date sous la forme d'un triplet
(j, m, a) de nombres entiers
:effet de bord: lecture d'une donnée sur l'entrée standard
:CU: validité des données lues
"""
while True:
reponse = input('j/m/a ? ')
reponse = reponse.split('/')
try:
assert len(reponse) == 3, 'il faut 2 /'
date = (int(reponse[0]), int(reponse[1]), int(reponse[2]))
return date
except AssertionError:
print('il faut 2 /')
except ValueError:
print('Date exprimée avec trois nombres svp !')
>>> lire_date3()
j/m/a ? 29 2 2016
il faut 2 /
j/m/a ? 29 / fevrier / 2016
Date exprimée avec trois nombres svp !
j/m/a ? 29 / 2 / 2016
(29, 2, 2016)
Cette fonction ne contrôle pas la validité de la date saisie :
>>> lire_date3()
j/m/a ? 290/20/2016
(290, 20, 2016)
Voici une quatrième version de la fonction lire_date
. Elle suppose défini un prédicat
date_valide
qui renvoie True
ou False
en fonction de la validité du triplet
d’entiers passé en paramètre pour représenter une date.
À noter que l’exception AssertionError
peut être déclenchée par deux instructions assert
,
et qu’il n’y a qu’une seule clause de traitement de cette exception. Le message imprimé à
destination de l’utilisateur est mis dans une variable err
.
def lire_date4():
"""
:return: (tuple) une date sous la forme d'un triplet
(j, m, a) de nombres entiers
:effet de bord: lecture d'une donnée sur l'entrée standard
:CU: validité des données lues
"""
while True:
reponse = input('j/m/a ? ')
reponse = reponse.split('/')
try:
assert len(reponse) == 3, 'il faut 2 /'
date = (int(reponse[0]), int(reponse[1]), int(reponse[2]))
assert date_valide(date), 'date non valide'
return date
except AssertionError as err:
print(err)
except ValueError:
print('Date exprimée avec trois nombres svp !')
>>> lire_date4()
j/m/a ? 290 20 2016
il faut 2 /
j/m/a ? 290/20/2016
date non valide
j/m/a ? 29/2/2016
(29, 2, 2016)
Avertissement
La version ci-dessous de la fonction lire_date
est un exemple à ne pas suivre
parce que
elle ne distingue aucune exception (ligne
except:
non accompagnée du nom de l’exception)elle inclut trop de code dans le traitement (lecture et découpage inclus dans le
try:
).
def lire_date5():
"""
:return: (tuple) une date sous la forme d'un triplet
(j, m, a) de nombres entiers
:effet de bord: lecture d'une donnée sur l'entrée standard
:CU: validité des données lues
"""
while True:
try:
reponse = input('j/m/a ? ')
reponse = reponse.split('/')
assert len(reponse) == 3
date = (int(reponse[0]), int(reponse[1]), int(reponse[2]))
assert date_valide(date), 'date non valide'
return date
except:
print('Date au format j/m/a svp !')
Une conséquence de l’absence de distinction des exceptions pouvant être déclenchées, fait que,
une fois appelée, la fonction lire_date5
attend patiemment qu’une date valide soit tapée
par l’utilisateur. Celui-ci ne peut pas interrompre l’exécution de cette fonction par la
combinaison de touches Ctrl+C (exception KeyboardInterrupt
), ni par le marqueur de fin de
fichier Ctrl+D (exception EOFError
), car chacune de ces exceptions est récupérée par la
partie except:
.
Notes¶
- 1
La réponse de Python sur la valeur de la variable
sys.stdin
dépend non seulement de la plateforme sur laquelle on travaille, mais aussi du logiciel utilisé. Ainsi la réponse mentionnée dans ce texte suppose qu’on utilise un simple interpréteur Python (python3
ouipython3
). Mais si on travaille avecIdle
, on obtient la réponse :>>> sys.stdin <idlelib.PyShell.PseudoInputFile object at 0x7f90ed9d3630>
(Le nombre en hexa
0x...
peut aussi ne pas être celui ci-dessus.)