Cours 4
Mécanisme d’exceptions et lecture de fichiers
Quentin Lurkin
Trace d’erreur
Une erreur d'exécution imprime une trace d'erreurChemin d'exécution complet qui a provoqué
l'erreur
defpercentage(score:float,total:int)->float:returnscore/total*100print('Alexis a obtenu',percentage(18,20),'%')print('Sébastien a obtenu',percentage(6,0),'%')
Alexis a obtenu 90.0 %
Traceback (most recent call last):
File "program.py", line 5, in <module>
print('Sébastien a obtenu', percentage(6, 0), '%')
File "program.py", line 2, in percentage
return score / total * 100
ZeroDivisionError: division by zero
Trace d’erreur
L'erreur a comme origine l'exécution de
l'instruction en ligne 5
File "program.py", line 5, in <module>
print('Sébastien a obtenu', percentage(6, 0), '%')
L'erreur est dans la fonction
percentage()
File "program.py", line 2, in percentage
return score / total * 100
L'erreur est de type division par zéro
ZeroDivisionError: division by zero
Types d’erreur
On peut considérer trois types d'erreur possibles
Erreur de syntaxeCode source
mal formé
Erreur d'exécutionExécution
d'une opération interdite
Erreur logiqueProgramme ne
calcule pas ce qu'il faut
Le troisième type est le plus difficile à décelerIl faut pouvoir vérifier que le programme fait ce
qu'il faut
Erreur de syntaxe
Erreur détectée lors de l'exécution de
l'instruction Python est en effet un langage
interprété
Code source du programme contient des fautes de
syntaxeUn peu comme l'orthographe en
français
score=12ifscore>10print('Vous avez réussi !')
File "program.py", line 2
if score > 10
^
SyntaxError: expected ':'
Erreur d'exécution
Erreur produite durant l'exécution d'une opération
interdite Division par zéro, indice en dehors d'une
liste...
Il peut être utile de décrire clairement ce que la fonction devrait
faire Pour s'assurer qu'il y a bien une
erreur
Documentation
La documentation d'une fonction décrit le résultat
produit Permet à un utilisateur d'interpréter le
résultat de l'appel
Description de conditions sur les paramètres Et de la valeur de retour si elles ne sont pas
satisfaites
defpercentage(score:float,total:int)->Optional[float]:"""Returns a grade as a percentage Args: `score`: the obtained grade. Must be positive and `<= total` `total`: the maximum grade obtainable. Must be positive Returns: the computed percentage """returnscore/total*100
Documentation
Conditions de la documentation non-respectéesLe programme continue de fonctionner avec une valeur
illogique
Vérification de conditions sensées être vraies avec
assertOn vérifie notamment les
conditions sur les paramètres avec cette instruction
Un programme doit fonctionner si on supprime les
assertions Elles ne doivent pas faire partie du code
fonctionnel
defpercentage(score:float,total:int)->float:"""Returns a grade as a percentage Args: `score`: the obtained grade. Must be positive and `<= total` `total`: the maximum grade obtainable. Must be positive Returns: the computed percentage """asserttotal>0,'total must be positive'assert0<=score,'score must be positive'assertscore<=total,'score must be <= total'returnscore/total*100
Instruction assert
Arrêt du programme en cas d'erreur d'assertion
Avec affichage d'un message
d'information
Empêche des erreurs qui ne devraient pas se
produire Le programme peut être modifié pour les
éviter
Augmente nos chances d’avoir une erreur d’exécution au lieu
d’une erreur de logiqueCe qui est trés
désirable
75.0 %
Traceback (most recent call last):
File "program.py", line 17, in <module>
print(percentage(22, 20), '%')
File "program.py", line 13, in percentage
assert score <= total, 'score must be <= total'
AssertionError: score must be <= total
Se pratique sur du code dont vous avez le
contrôle
Gestion d’erreurs
Prendre en compte tous les cas possibles
d'exécution Utilisation de
if-else
fromtypingimportOptionaldefpercentage(score:float,total:int)->Optional[float]:"""Returns a grade as a percentage Args: `score`: the obtained grade `total`: the maximum grade obtainable Returns: the computed percentage if `total <= 0`, `score < 0` or `score > total`, then returns `None` """iftotal>0and(0<=score<=total):returnscore/total*100returnNone
Alexis a obtenu 90.0 %
Sébastien a obtenu None %
Impact sur l’annotation du type de retour de la
fonction
Doit être pratiquée pour interface avec l'extérieur Vérification de toutes données hors contrôle
Mecanisme d’exceptions
Gestion d’un code qui pourrait planter
Code risqué placé dans un bloc tryN'y placer que le code risqué et tout code qui en
dépend
Erreurs capturées dans le bloc exceptY placer le code à exécuter en cas de capture d'une
erreur
fromdatetimeimport*birthyear=input('Année de naissance ? ')try:now=datetime.now()age=now.year-int(birthyear)print('Tu as',age,'ans')except:print('Erreur')
Instruction try-except
Si l'utilisateur entre un nombre entier, pas
d'erreurs
Année de naissance ? 1982
Tu as 36 ans
Si l'utilisateur n'entre pas un nombre entier, erreur
capturée
Année de naissance ? BLA
Erreur
Validité d'une donnée
Demande d'une valeur à l'utilisateur en boucleTant que la valeur demandée n'est pas du bon
type
fromdatetimeimport*valid=Falsewhilenotvalid:birthyear=input('Année de naissance ? ')try:birthyear=int(birthyear)valid=Trueexcept:print('Veuillez entrer un nombre entier')now=datetime.now()age=now.year-birthyearprint('Tu as',age,'ans')
Vérifier le type d'erreur
Plusieurs types d'erreur sont possibles Division par zéro, erreur de conversion...
Toutes les erreurs sont capturées par l'instruction
exceptPossibilité de capturer
les erreurs de manière spécifique
a ? deux
<class 'ValueError'>
invalid literal for int() with base 10: 'deux'
Erreur de conversion
a ? 2
b ? 0
<class 'ZeroDivisionError'>
division by zero
Capturer une erreur spécifique
Gestionnaire d'erreurs différent pour chaque type
d'erreur Il suffit de déclarer un bloc
except par erreur à capturer
Attention à l'ordre de capture (de haut en bas)
Il faut classer les erreurs de la plus à la moins
spécificité
importsystry:a=int(input('a ? '))b=int(input('b ? '))print(a,'/',b,'=',a/b)exceptValueError:print('Erreur de conversion')exceptZeroDivisionError:print('Division par zéro')except:print('Autre erreur')
Capturer une erreur spécifique
importsystry:a=int(input('a ? '))b=int(input('b ? '))print(a,'/',b,'=',a/b)exceptException:print('Erreur')exceptValueError:print('Erreur de conversion')exceptZeroDivisionError:print('Division par zéro')
a ? 2
b ? 0
Autre erreur
Information sur une erreur
L'objet de l'exception peut contenir de
l'information On peut accéder à des propriétés ou à
des méthodes
can't assign to literal (mymod.py, line 1)
File: /Users/combefis/Desktop/mymod.py
Line: 1
Text: 2 = x
Gestionnaire d'erreurs partagé
Même gestionnaire d'erreurs pour différents types
Tuple d'exception fourni à l'instruction except
try:a=int(input('a ? '))b=int(input('b ? '))print(a/b)except(ZeroDivisionError,ValueError):print('Erreur de calcul')except:print('Erreur')
a ? 1
b ? 0
Erreur de calcul
Propagation d'erreur
Une erreur non capturée remonte les appels de
fonction Jusqu'à être attrapée ou remonté jusqu'au
bout
La trace d'erreur montre le trajet pris par
l'exception En la lisant à l'envers, on peut suivre
la propagation
Propagation d'erreur
Passage de fun à
compute au programme principal
deffun():print(1/0)defcompute():fun()compute()
Traceback (most recent call last):
File "program.py", line 7, in <module>
compute()
File "program.py", line 5, in compute
fun()
File "program.py", line 2, in fun
print(1 / 0)
ZeroDivisionError: division by zero
L'instruction raise permet de générer une erreur
Création d'un objet du type de
l'exception
deffact(n:int)->int:"""Compute the factorial of `n` Args: `n`: a positive integer Returns: The factorial of `n` Raises: ArithmeticError: if `n` is negative """ifn<0:raiseArithmeticError()ifn==0:return1returnn*fact(n-1)try:n=int(input('Entrez un nombre : '))print(fact(n))exceptArithmeticError:print('Veuillez entrer un nombre positif.')except:print('Veuillez entrer un nombre.')
Entrez un nombre : -12
Veuillez entrer un nombre positif.
Définir une erreur
Définition d'une erreur en définissant une nouvelle
classe La classe est créée à partir de la classe
Exception
L'instruction pass ne fait rien
frommathimportsqrtclassNoRootException(Exception):passdeftrinomialroots(a:float,b:float,c:float)->tuple[float,...]:"""Compute the real roots of a second-degree equation Args: `a`, `b` and `c`: The coefficients of the second-degree equation Returns: A tuple with one or two real roots Raises: NoRootException: if the equation has no real roots """delta=b**2-4*a*cifdelta<0:raiseNoRootException()ifdelta==0:return(-b/(2*a),)x1=(-b+sqrt(delta))/(2*a)x2=(-b-sqrt(delta))/(2*a)return(x1,x2)
Définir une erreur
Capture de la nouvelle erreur avec l'instruction
exceptLe nouveau
type d'erreur est maintenant connu par Python
try:print(trinomialroots(1,0,2))exceptNoRootException:print('Pas de racine réelle.')except:print('Erreur')
Pas de racine réelle.
Exception paramétrée
Stockage d'un paramètre dans l'exception
frommathimportsqrtclassNoRootException(Exception):def__init__(self,delta):self.delta=deltadeftrinomialroots(a:float,b:float,c:float)->tuple[float,...]:"""Compute the real roots of a second-degree equation Args: `a`, `b` and `c`: The coefficients of the second-degree equation Returns: A tuple with one or two real roots Raises: NoRootException: if the equation has no real roots """delta=b**2-4*a*cifdelta<0:raiseNoRootException(delta)ifdelta==0:return(-b/(2*a),)x1=(-b+sqrt(delta))/(2*a)x2=(-b-sqrt(delta))/(2*a)return(x1,x2)try:print(trinomialroots(1,0,2))exceptNoRootExceptionase:print(f'Pas de racine réelle (delta = {e.delta})')except:print('Erreur')
Pas de racine réelle (delta = -8)
Exception avec dataclass
frommathimportsqrtfromdataclassesimportdataclass@dataclassclassNoRootException(Exception):delta:floatdeftrinomialroots(a:float,b:float,c:float)->tuple[float,...]:"""Compute the real roots of a second-degree equation Args: `a`, `b` and `c`: The coefficients of the second-degree equation Returns: A tuple with one or two real roots Raises: NoRootException: if the equation has no real roots """delta=b**2-4*a*cifdelta<0:raiseNoRootException(delta)ifdelta==0:return(-b/(2*a),)x1=(-b+sqrt(delta))/(2*a)x2=(-b-sqrt(delta))/(2*a)return(x1,x2)try:print(trinomialroots(1,0,2))exceptNoRootExceptionase:print(f"Pas de racine réelle (delta = {e.delta})")except:print("Erreur")
Fichier
Un fichier stocke des informations sur le disque
dur Il est créé, modifié, supprimé
Manipulation des fichiers grâce au système
d'exploitationL'interpréteur fait le
relais avec Linux, Windows, Mac OS X...
Des informations sont associées à un fichier Nom, taille, date de création, dernière date de
modification...
Type de fichier
Au plus bas niveau, un fichier est une séquence de
bits (0 et 1) La séquence doit
être interprétée pour en obtenir le sens
Deux types de fichiers manipulés à des niveaux
différents
Fichier texte est une séquence de caractères
Fichier binaire est une séquence d'octets (8 bits)
Chemin
Fichier identifié sur une machine à l'aide de son
cheminIdentifie l'endroit où se
situe le fichier
Deux façons de spécifier un chemin
Chemin absolu à partir de la racine C:\ (Windows), / (OSX et Linux)
Chemin relatif à partir du répertoire courant
Chemin relatif
Chemin absolu
data.txt
C:\Users\lur\Desktop\data.txt
src\program.py
C:\Users\lur\Desktop\src\program.py
..\image.png
C:\Users\lur\image.png
..\movies\hamburgers.mp4
C:\Users\lur\movies\hamburgers.mp4
Navigation en ligne de commande
Pour passer d'un répertoire à l'autre dans une console de
commandes
pwd: Affiche le chemin du
répertoire courant Base des chemins
relatifs
cd(suivi d'un chemin):
Change de répertoire courant .. pour le répertoire parent
dir(Windows) ou ls(OSX et Linux): Liste le
contenu du répertoire
Ouverture d'un fichier
Pour manipuler un fichier, il faut d'abord l'ouvrirOn utilise la fonction open, qui renvoie un identifiant de
fichier
Erreurs possibles lors de l'ouverture Fichier introuvable, …
try:file=open('data.txt')exceptFileNotFoundError:print('Le fichier est introuvable')exceptIOError:print("Erreur d'ouverture")
Mode d'ouverture
Par défaut, fichier ouvert en lecture seuleTout ce qu'on peut faire c'est donc lire le contenu du
fichier
On peut spécifier le mode d'ouverture désiré Avec le deuxième paramètre de la fonction
open()
try:file=open('data.txt','w')# Ouverture en écritureexceptIOError:print("Erreur d'ouverture")
Mode d'ouverture
Mode d'ouverture définit avec des caractères
Caractère
Description
r
Lecture (par défaut)
w
Écriture (avec remise à zéro)
x
Création exclusive (erreur si fichier déjà existant)
a
Écriture (avec ajout à la fin)
b
Mode binaire
t
Mode texte (par défaut)
try:file=open('data.txt','rt')# Mode par défautexceptFileNotFoundError:print('Le fichier est introuvable')exceptIOError:print("Erreur d'ouverture")
Fermeture d'un fichier
Une fois les opérations finies, il faut fermer le
fichier On utilise la méthode close
avec l'identifiant de fichier
Libération des ressources et sauvegarde sur disque
Le système d'exploitation limite le nombre de
fichiers ouverts
try:file=open('data.txt')file.close()# Fermeture du fichierexceptFileNotFoundError:print('Le fichier est introuvable')exceptIOError:print("Erreur d'ouverture")
Lecture
Lecture intégrale du fichier comme une chaîne de
caractères On utilise la méthode read
avec l'identifiant de fichier
La lecture peut échouer et provoquer une exception
IOErrorPar exemple si le
disque est déconnecté pendant la lecture
try:file=open('data.txt')print(file.read())file.close()exceptFileNotFoundError:print('Le fichier est introuvable')exceptIOError:print("Erreur d'entrée/sortie")
Instruction finally
En cas d'erreur, le fichier pourrait ne pas être
ferméCar l'exécution du code saute
directement dans l'except
Instruction finally exécutée dans tous
les cas Après la fin du bloc try ou
après un except éventuel
try:file=open('data.txt')print(file.read())exceptFileNotFoundError:print('Le fichier est introuvable')exceptIOError:print("Erreur d'ouverture")finally:file.close()
Instruction finally
Bug dans le code précédent si le fichier n'a pas su
être ouvert La variable file ne sera
pas initialisée et close pas disponible
On utilise une instruction try/finally
additionnelle
try:file=open('data.txt')try:print(file.read())finally:file.close()exceptFileNotFoundError:print('Le fichier est introuvable')exceptIOError:print("Erreur d'entrée/sortie")
Instruction with
Instruction with pour fermeture propre
des ressources L'appel à close sera
fait automatiquement
Il faut garder le try/except pour les
IOError
try:withopen('data.txt')asfile:print(file.read())exceptFileNotFoundError:print('Le fichier est introuvable')exceptIOError:print("Erreur d'entrée/sortie")
Écriture
Écriture en ajoutant des chaines de caractères au
fichier On utilise la fonction write
avec l'identifiant de fichier
L'écriture peut échouer et provoquer une exception
IOErrorPar exemple si
l'espace disque devient plein pendant l'écriture
withopen('out.txt','w')asfile:file.write('Table de 10\n')foriinrange(10):file.write(f'{i} x 10 = {i*10}\n')
Copie d'un fichier
Copie en faisant une lecture puis écriture du
contenu lu Deux instruction with
imbriquées
Si le fichier destination existe déjà, il est
effacéIl faut utiliser le mode
x au lieu de w pour empêcher cela
withopen('data.txt')asfile:forlineinfile:cleaned=line.rstrip()tokens=cleaned.split(':')print(f'Compte {tokens[0]} de {tokens[2]} (mode de passe : {tokens[3]})')
Compte Facebook de lurk (mode de passe : 8dj,Sj0m1)
Compte Skype de cedou (mode de passe : arduino)
Compte Facebook de fingerfood (mode de passe : b8ur,g2er)
Lecture ligne par ligne
L'itérateur est un raccourci d'appel de
readlineLis une ligne s'il en reste à
lire dans le fichier
Renvoie une chaine de caractères vide lorsqu'au
bout du fichier Pratique lorsqu'on sait combien de
lignes lire
withopen('data.txt')asfile:line=file.readline()whileline!='':cleaned=line.rstrip()tokens=cleaned.split(':')print(f'Compte {tokens[0]} de {tokens[2]} (mode de passe : {tokens[3]})')line=file.readline()
Lecture ligne par ligne
La fonction readlines lis l'intégralité des
lignes en une traite La fonction renvoie
une liste de chaines de caractères
On peut supprimer la variablecleaned
inutile En enchainant directement les appels à
rstrip et split
withopen('data.txt')asfile:content=file.readlines()forlineincontent:tokens=line.rstrip().split(':')print(f'Compte {tokens[0]} de {tokens[2]} (mode de passe : {tokens[3]})')
Lecture ligne par ligne
Amélioration du code avec une fonction de formatage
On définit une fonction qui formate une
ligne
On définit une liste par compréhension et on joint ses éléments
Jointure des éléments d'une liste réalisée avec
fonction join
defformat(line:str)->str:tokens=line.rstrip().split(':')returnf'Compte {tokens[0]} de {tokens[2]} (mode de passe : {tokens[3]})'withopen('data.txt')asfile:content=file.readlines()print('\n'.join([format(line)forlineincontent]))
Exception
L'erreur principale d'entrée/sortie est
IOErrorOn peut se limiter à capturer
cette unique erreur
Erreur spécialisée selon le type précis
FileNotFoundError, si le fichier n'est pas trouvé
FileExistsError, si le fichier existe déjà
PermissionError, si l'utilisateur n'a pas les droits
d'accès
IsADirectoryError, si le fichier est en fait un
dossier
Encodage
Les caractères sont stockés au format binaire dans
l'ordinateur Table de correspondance associant un
entier à chaque caractère
La table de caractères ASCII (iso-646) contient 128
caractères Table suffisante pour des textes en
anglais
0
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F
0
NUL
SOH
STH
ETH
EOT
ENQ
ACK
BEL
BS
HT
LF
VT
FF
CR
SO
SI
1
DLE
DC1
CD2
DC3
DC4
NAK
SYN
ETB
CAN
EM
SUB
ESC
FS
GS
RS
US
2
spc
!
"
#
$
%
&
'
(
)
*
+
,
-
.
/
3
0
1
2
3
4
5
6
7
8
9
:
;
<
=
>
?
4
@
A
B
C
D
E
F
G
H
I
J
K
L
M
N
O
5
P
Q
R
S
T
U
V
W
X
Y
Z
[
\
]
^
_
6
`
a
b
c
d
e
f
g
h
i
j
k
l
m
n
o
7
p
q
r
s
t
u
v
w
x
y
z
{
|
}
~
DEL
Encodage
La fonction ord donne le code d'un
caractère Sous forme d'un nombre entier
La fonction chr donne le caractère
correspondant à un code Sous forme d'une chaine de
caractères
Traceback (most recent call last):
File "program.py", line 5, in
print(file.read())
File "/Library/Frameworks/Python.framework/Versions/3.4/lib/python3.4/encodings/ascii.py", line 26, in decode
return codecs.ascii_decode(input, self.errors)[0]
UnicodeDecodeError: 'ascii' codec can't decode byte 0xf0 in position 11: ordinal not in range(128)