Introduction
Bien que les exceptions aient été introduites plus tôt, cette section complète quelques détails supplémentaires sur la vérification d'erreurs et la gestion d'exceptions.
Comment les programmes échouent
Python ne effectue aucune vérification ni validation des types ou des valeurs des arguments de fonction. Une fonction fonctionnera avec n'importe quel données compatible avec les instructions de la fonction.
def add(x, y):
return x + y
add(3, 4) ## 7
add('Hello', 'World') ## 'HelloWorld'
add('3', '4') ## '34'
Si des erreurs se produisent dans une fonction, elles apparaissent au moment de l'exécution (en tant qu'exception).
def add(x, y):
return x + y
>>> add(3, '4')
Traceback (most recent call last):
...
TypeError: unsupported operand type(s) for +:
'int' and 'str'
>>>
Pour vérifier le code, il y a une forte mise sur les tests (traité plus tard).
Exceptions
Les exceptions sont utilisées pour signaler des erreurs. Pour lever une exception vous-même, utilisez l'instruction raise.
if name not in authorized:
raise RuntimeError(f'{name} not authorized')
Pour attraper une exception, utilisez try-except.
try:
authenticate(username)
except RuntimeError as e:
print(e)
Gestion d'exceptions
Les exceptions se propagent jusqu'au premier except correspondant.
def grok():
...
raise RuntimeError('Whoa!') ## Exception levée ici
def spam():
grok() ## Appel qui entraînera une exception
def bar():
try:
spam()
except RuntimeError as e: ## Exception capturée ici
...
def foo():
try:
bar()
except RuntimeError as e: ## L'exception n'arrive PAS ici
...
foo()
Pour gérer l'exception, placez des instructions dans le bloc except. Vous pouvez ajouter n'importe quelles instructions pour gérer l'erreur.
def grok():...
raise RuntimeError('Whoa!')
def bar():
try:
grok()
except RuntimeError as e: ## Exception capturée ici
instructions ## Utilisez ces instructions
instructions
...
bar()
Après la gestion, l'exécution reprend avec la première instruction après le try-except.
def grok():...
raise RuntimeError('Whoa!')
def bar():
try:
grok()
except RuntimeError as e: ## Exception capturée ici
instructions
instructions
...
instructions ## Reprend l'exécution ici
instructions ## Et continue ici
...
bar()
Exceptions intégrées
Il existe environ une vingtaine d'exceptions intégrées. Généralement, le nom de l'exception indique ce qui ne va pas (par exemple, une ValueError est levée parce que vous avez fourni une mauvaise valeur). Ce n'est pas une liste exhaustive. Consultez la documentation pour en savoir plus.
ArithmeticError
AssertionError
EnvironmentError
EOFError
ImportError
IndexError
KeyboardInterrupt
KeyError
MemoryError
NameError
ReferenceError
RuntimeError
SyntaxError
SystemError
TypeError
ValueError
Valeurs d'exception
Les exceptions ont une valeur associée. Elle contient des informations plus spécifiques sur ce qui ne va pas.
raise RuntimeError('Invalid user name')
Cette valeur est partie de l'instance d'exception qui est placée dans la variable fournie à except.
try:
...
except RuntimeError as e: ## `e` contient l'exception levée
...
e est une instance du type d'exception. Cependant, elle semble souvent une chaîne de caractères lorsqu'elle est affichée.
except RuntimeError as e:
print('Failed : Reason', e)
Capturer plusieurs erreurs
Vous pouvez capturer différents types d'exceptions en utilisant plusieurs blocs except.
try:
...
except LookupError as e:
...
except RuntimeError as e:
...
except IOError as e:
...
except KeyboardInterrupt as e:
...
Alternativement, si les instructions pour les gérer sont les mêmes, vous pouvez les regrouper :
try:
...
except (IOError,LookupError,RuntimeError) as e:
...
Capturer toutes les erreurs
Pour capturer n'importe quelle exception, utilisez Exception comme ceci :
try:
...
except Exception: ## DANGER. Voir ci-dessous
print('An error occurred')
En général, écrire du code de cette manière est une mauvaise idée car vous n'aurez aucune idée du pourquoi de son échec.
Mauvaise manière de capturer les erreurs
Voici la mauvaise manière d'utiliser les exceptions.
try:
go_do_something()
except Exception:
print('Computer says no')
Cela capture toutes les erreurs possibles et peut rendre impossible le débogage lorsque le code échoue pour une raison que vous n'attendiez absolument pas (par exemple, un module Python non installé, etc.).
Une approche un peu meilleure
Si vous allez capturer toutes les erreurs, c'est une approche plus raisonnable.
try:
go_do_something()
except Exception as e:
print('Computer says no. Reason :', e)
Il indique une raison spécifique pour l'échec. Il est presque toujours une bonne idée d'avoir un mécanisme pour visualiser / signaler les erreurs lorsque vous écrivez du code qui capture toutes les exceptions possibles.
En général cependant, il est préférable de capturer l'erreur de la manière la plus limitée possible. Capturer seulement les erreurs que vous pouvez réellement gérer. Laissez les autres erreurs passer - peut-être que du code autre peut les gérer.
Reprovoquer une exception
Utilisez raise pour propager une erreur capturée.
try:
go_do_something()
except Exception as e:
print('Computer says no. Reason :', e)
raise
Cela vous permet de prendre des mesures (par exemple, journalisation) et de transmettre l'erreur à l'appelant.
Meilleures pratiques en matière d'exceptions
Ne capturez pas les exceptions. Échoue rapidement et fortement. Si c'est important, quelqu'un d'autre s'occupera du problème. Capturez une exception seulement si vous êtes celui-là. C'est-à-dire, capturez seulement les erreurs où vous pouvez récupérer et continuer de manière raisonnable.
Instruction finally
Elle spécifie le code qui doit s'exécuter que l'exception se produise ou non.
lock = Lock()
...
lock.acquire()
try:
...
finally:
lock.release() ## ceci sera TOUJOURS exécuté. Avec ou sans exception.
Utilisé couramment pour gérer de manière sûre les ressources (en particulier les verrous, les fichiers, etc.).
Instruction with
Dans le code moderne, try-finally est souvent remplacé par l'instruction with.
lock = Lock()
with lock:
## verrou acquis
...
## verrou libéré
Un exemple plus familier :
with open(filename) as f:
## Utiliser le fichier
...
## Fichier fermé
with définit un contexte d'utilisation pour une ressource. Lorsque l'exécution quitte ce contexte, les ressources sont libérées. with ne fonctionne qu'avec certains objets qui ont été spécifiquement programmés pour le supporter.
Exercice 3.8 : Reprovoquer des exceptions
La fonction parse_csv() que vous avez écrite dans la section précédente permet de sélectionner des colonnes spécifiées par l'utilisateur, mais cela ne fonctionne que si le fichier de données d'entrée a des en-têtes de colonne.
Modifiez le code de manière à ce qu'une exception soit levée si les arguments select et has_headers=False sont tous les deux passés. Par exemple :
>>> parse_csv('/home/labex/project/prices.csv', select=['name','price'], has_headers=False)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "fileparse.py", line 9, in parse_csv
raise RuntimeError("select argument requires column headers")
RuntimeError: select argument requires column headers
>>>
Ayant ajouté ce contrôle, vous pourriez vous demander si vous devriez effectuer d'autres types de vérifications de cohérence dans la fonction. Par exemple, devriez-vous vérifier que le nom de fichier est une chaîne de caractères, que types est une liste, ou quelque chose de ce genre?
En règle générale, il est généralement préférable de sauter de telles vérifications et de laisser le programme échouer sur des entrées erronées. Le message de traceback indiquera la source du problème et peut aider à déboguer.
La principale raison d'ajouter le contrôle ci-dessus est d'éviter d'exécuter le code dans un mode absurde (par exemple, en utilisant une fonctionnalité qui nécessite des en-têtes de colonne, mais en spécifiant simultanément qu'il n'y a pas d'en-têtes).
Cela indique une erreur de programmation de la part du code appelant. Vérifier les cas qui "ne devraient pas arriver" est souvent une bonne idée.
Exercice 3.9 : Capturer des exceptions
La fonction parse_csv() que vous avez écrite est utilisée pour traiter le contenu complet d'un fichier. Cependant, dans le monde réel, il est possible que les fichiers d'entrée soient corrompus, manquants ou contiennent des données sales. Essayez cet exemple :
>>> portfolio = parse_csv('missing.csv', types=[str, int, float])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "fileparse.py", line 36, in parse_csv
row = [func(val) for func, val in zip(types, row)]
ValueError: invalid literal for int() with base 10: ''
>>>
Modifiez la fonction parse_csv() pour capturer toutes les exceptions ValueError générées pendant la création d'un enregistrement et afficher un message d'avertissement pour les lignes qui ne peuvent pas être converties.
Le message devrait inclure le numéro de ligne et des informations sur la raison pour laquelle la conversion a échoué. Pour tester votre fonction, essayez de lire le fichier missing.csv ci-dessus. Par exemple :
>>> portfolio = parse_csv('missing.csv', types=[str, int, float])
Ligne 4: Impossible de convertir ['MSFT', '', '51.23']
Ligne 4: Raison : invalid literal for int() with base 10: ''
Ligne 7: Impossible de convertir ['IBM', '', '70.44']
Ligne 7: Raison : invalid literal for int() with base 10: ''
>>>
>>> portfolio
[{'name': 'AA','shares': 100, 'price': 32.2}, {'name': 'IBM','shares': 50, 'price': 91.1}, {'name': 'CAT','shares': 150, 'price': 83.44}, {'name': 'GE','shares': 95, 'price': 40.37}, {'name': 'MSFT','shares': 50, 'price': 65.1}]
>>>
Exercice 3.10 : Rétablir le silence des erreurs
Modifiez la fonction parse_csv() de sorte que les messages d'erreur de l'analyse puissent être silencés si l'utilisateur le souhaite explicitement. Par exemple :
>>> portfolio = parse_csv('missing.csv', types=[str,int,float], silence_errors=True)
>>> portfolio
[{'name': 'AA','shares': 100, 'price': 32.2}, {'name': 'IBM','shares': 50, 'price': 91.1}, {'name': 'CAT','shares': 150, 'price': 83.44}, {'name': 'GE','shares': 95, 'price': 40.37}, {'name': 'MSFT','shares': 50, 'price': 65.1}]
>>>
La gestion des erreurs est l'une des choses les plus difficiles à bien faire dans la plupart des programmes. En règle générale, vous ne devriez pas ignorer silencieusement les erreurs. Au contraire, il est préférable de signaler les problèmes et de donner à l'utilisateur l'option de silencer le message d'erreur s'il le souhaite.
Sommaire
Félicitations ! Vous avez terminé le laboratoire de vérification d'erreurs. Vous pouvez pratiquer d'autres laboratoires sur LabEx pour améliorer vos compétences.