Gestion des erreurs et exceptions

PythonPythonBeginner
Pratiquer maintenant

This tutorial is from open-source community. Access the source code

💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici

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.

✨ Vérifier la solution et pratiquer

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}]
>>>
✨ Vérifier la solution et pratiquer

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.

✨ Vérifier la solution et pratiquer

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.