Dans ce didacticiel, vous apprendrez à gérer les conditions d'erreur dans Python d'un point de vue système complet. La gestion des erreurs est un aspect critique de la conception et elle s’étend des niveaux les plus bas (parfois du matériel) aux utilisateurs finaux. Si vous n'avez pas de stratégie cohérente en place, votre système sera peu fiable, l'expérience utilisateur sera médiocre et vous aurez beaucoup de difficultés à déboguer et à dépanner.
La clé du succès consiste à prendre conscience de tous ces aspects interdépendants, à les considérer explicitement et à former une solution qui aborde chaque point.
Il existe deux principaux modèles de gestion des erreurs: les codes d'état et les exceptions. Les codes d'état peuvent être utilisés par n'importe quel langage de programmation. Les exceptions nécessitent un support linguistique / d'exécution.
Python supporte les exceptions. Python et sa bibliothèque standard utilisent des exceptions de manière libérale pour signaler de nombreuses situations exceptionnelles telles que les erreurs d'E / S, la division par zéro, l'indexation hors limites, ainsi que des situations moins exceptionnelles telles que la fin de l'itération (même si elle est masquée). La plupart des bibliothèques emboîtent le pas et lèvent des exceptions.
Cela signifie que votre code devra de toute façon gérer les exceptions soulevées par Python et ses bibliothèques. Vous pouvez donc également lever des exceptions à votre code si nécessaire et ne pas vous fier aux codes d'état..
Avant de plonger dans le sanctuaire interne des exceptions Python et des meilleures pratiques de gestion des erreurs, voyons quelques opérations de gestion des exceptions en action:
def f (): return 4/0 def g (): raise Exception ("Ne nous appelez pas. Nous vous appellerons") def h (): try: f () sauf Exception en tant que e: print (e) try: g () sauf Exception en tant que e: print (e)
Voici la sortie lors de l'appel h ()
:
h () division par zéro Ne nous appelez pas. Nous vous appellerons
Les exceptions Python sont des objets organisés dans une hiérarchie de classes..
Voici toute la hiérarchie:
BaseException + - SystemExit + - KeyboardInterrupt + - GeneratorExit + - Exception + - StopIteration + - StandardError | + - BufferError | + - ArithmeticError | | + - FloatingPointError | | + - OverflowError | | + - ZeroDivisionError | + - AssertionError | + - AttributeError | + - EnvironmentError | | + - IOError | | + - OSError | | + - WindowsError (Windows) | | + - VMSError (VMS) | + - EOFError | + - ImportError | + - LookupError | | + - IndexError | | + - KeyError | + - MemoryError | + - NameError | | + - UnboundLocalError | + - ReferenceError | + - RuntimeError | | + - NotImplementedError | + - SyntaxError | | + - IndentationError | | + - TabError | + - SystemError | + - TypeError | + - ValueError | + - UnicodeError | + - UnicodeDecodeError | + - UnicodeEncodeError | + - UnicodeTranslateError + - Avertissement + - DeprecationWarning + - PendingDeprecationWarning + - RuntimeWarning + - SyntaxWarning + - UserWarning + - FutureWarning + - ImportWarning + - UnicodeWarning + - BytesWarning
Plusieurs exceptions spéciales dérivent directement de BaseException
, comme SystemExit
, ClavierInterrupt
et GeneratorExit
. Puis il y a le Exception
classe, qui est la classe de base pour StopIteration
, Erreur standard
et Attention
. Toutes les erreurs types proviennent de Erreur standard
.
Lorsque vous déclenchez une exception ou qu'une fonction que vous avez appelée déclenche une exception, le flux de code normal se termine et l'exception commence à se propager jusqu'à la pile d'appels jusqu'à ce qu'elle rencontre un gestionnaire d'exceptions approprié. Si aucun gestionnaire d'exception n'est disponible pour le gérer, le processus (ou plus précisément le thread actuel) sera terminé avec un message d'exception non géré..
Relever des exceptions est très facile. Vous venez d'utiliser le élever
mot-clé pour élever un objet qui est une sous-classe du Exception
classe. Ce pourrait être un exemple de Exception
elle-même, une des exceptions standard (par exemple. Erreur d'exécution
) ou une sous-classe de Exception
vous vous êtes dérivé. Voici un petit extrait qui montre tous les cas:
# Raise une instance de la classe Exception elle-même raise Exception ('Ummm… quelque chose ne va pas') # # Monte une instance de la classe RuntimeError raise RuntimeError ('Ummm… quelque chose ne va pas') # # Crée une sous-classe personnalisée d'Exception qui conserve l'horodatage l’exception a été créée à partir de la classe d’importation datetime datetime SuperError (Exception): def __init __ (self, message): Exception .__ init __ (message) self.when = datetime.now () déclenche SuperError ('Ummm… quelque chose ne va pas')
Vous attrapez des exceptions avec le sauf
clause, comme vous l'avez vu dans l'exemple. Lorsque vous attrapez une exception, vous avez trois options:
Vous devez avaler l’exception si vous savez comment la gérer et que vous pouvez vous rétablir complètement..
Par exemple, si vous recevez un fichier d'entrée de formats différents (JSON, YAML), vous pouvez essayer de l'analyser à l'aide de différents analyseurs. Si l'analyseur JSON a généré une exception indiquant que le fichier n'est pas un fichier JSON valide, avalez-le et essayez-le avec l'analyseur YAML. Si l'analyseur YAML échoue aussi, vous laissez l'exception se propager.
importer json importer yaml def parse_file (nom du fichier): essayer: renvoyer json.load (ouvrir (nom du fichier)) sauf json.JSONDecodeError retourner yaml.load (ouvrir (nom du fichier))
Notez que d'autres exceptions (par exemple, fichier non trouvé ou aucune autorisation de lecture) se propageront et ne seront pas interceptées par la clause except spécifique. Ceci est une bonne politique dans ce cas où vous souhaitez essayer l'analyse YAML uniquement si l'analyse JSON a échoué en raison d'un problème de codage JSON..
Si vous voulez gérer tout les exceptions alors juste utiliser sauf exception
. Par exemple:
def print_exception_type (func, * args, ** kwargs): essayez: renvoyer func (* args, ** kwargs) sauf exception comme e: print type (e)
Notez qu'en ajoutant comme e
, vous liez l'objet exception au nom e
disponible dans votre clause d'exception.
Pour re-relancer, il suffit d'ajouter élever
sans arguments à l'intérieur de votre gestionnaire. Cela vous permet d'effectuer certaines manipulations locales, tout en laissant également les niveaux supérieurs le gérer également. Ici le invoke_function ()
function affiche le type d'exception sur la console puis relance l'exception.
def invoke_function (func, * args, ** kwargs): essaye: renvoie func (* args, ** kwargs) sauf exception comme e: print type (e)
Il existe plusieurs cas où vous voudriez soulever une exception différente. Parfois, vous souhaitez regrouper plusieurs exceptions de bas niveau différentes dans une seule catégorie gérée uniformément par un code de niveau supérieur. Dans certains cas, vous devez transformer l'exception au niveau utilisateur et fournir un contexte spécifique à l'application..
Parfois, vous voulez vous assurer que du code de nettoyage est exécuté même si une exception a été déclenchée quelque part au cours du processus. Par exemple, vous pouvez avoir une connexion de base de données que vous souhaitez fermer une fois que vous avez terminé. Voici la mauvaise façon de le faire:
def fetch_some_data (): db = open_db_connection () requête (db) close_db_Connection (db)
Si la question()
la fonction déclenche une exception puis l'appel à close_db_connection ()
ne sera jamais exécuté et la connexion à la base de données restera ouverte. le enfin
La clause est toujours exécutée après l’essai du gestionnaire d’exception. Voici comment le faire correctement:
def fetch_some_data (): db = None essayez: db = open_db_connection () query (db) finally: si db n'est pas None: close_db_connection (db)
L'appel à open_db_connection ()
ne peut pas renvoyer une connexion ou déclencher une exception elle-même. Dans ce cas, il n'est pas nécessaire de fermer la connexion à la base de données..
Lors de l'utilisation enfin
, vous devez faire attention à ne pas soulever d'exceptions car elles masqueront l'exception d'origine.
Les gestionnaires de contextes offrent un autre mécanisme pour encapsuler des ressources telles que des fichiers ou des connexions à une base de données dans un code de nettoyage qui s'exécute automatiquement même lorsque des exceptions ont été déclenchées. Au lieu de blocs try-finally, vous utilisez le avec
déclaration. Voici un exemple avec un fichier:
def fichier_fichier (nom_fichier): avec open (nomfichier) comme f: process (f.read ())
Maintenant, même si processus()
une exception, le fichier sera fermé correctement dès que le champ d'application de la avec
le bloc est quitté, que l'exception soit gérée ou non.
La journalisation est quasiment une exigence dans les systèmes non triviaux à fonctionnement long. Cela est particulièrement utile dans les applications Web où vous pouvez traiter toutes les exceptions de manière générique: il suffit de consigner l'exception et de renvoyer un message d'erreur à l'appelant..
Lors de la journalisation, il est utile de consigner le type d'exception, le message d'erreur et le stacktrace. Toutes ces informations sont disponibles via le sys.exc_info
objet, mais si vous utilisez le logger.exception ()
méthode dans votre gestionnaire d'exceptions, le système de journalisation Python extraira toutes les informations pertinentes pour vous.
C'est la meilleure pratique que je recommande:
import logging logger = logging.getLogger () def f (): essayez: flaky_func () sauf exception: logger.exception () raise
Si vous suivez ce modèle alors (en supposant que vous configurez la journalisation correctement), peu importe ce qui se passe, vous aurez un très bon enregistrement dans vos journaux de ce qui a mal tourné et vous pourrez résoudre le problème..
Si vous relancez, assurez-vous de ne pas consigner la même exception encore et encore à des niveaux différents. C'est un gaspillage, et cela pourrait vous dérouter et vous faire penser que plusieurs instances du même problème se sont produites, alors qu'en pratique une seule instance a été journalisée plusieurs fois..
Le moyen le plus simple de le faire est de laisser toutes les exceptions se propager (à moins qu'elles ne puissent être traitées avec confiance et avalées plus tôt), puis de consigner au plus haut niveau de votre application / système..
La journalisation est une capacité. La mise en œuvre la plus courante consiste à utiliser des fichiers journaux. Mais, pour les systèmes distribués à grande échelle avec des centaines, des milliers ou plus de serveurs, ce n'est pas toujours la meilleure solution..
Pour garder une trace des exceptions sur l’ensemble de votre infrastructure, un service tel que Sentry est extrêmement utile. Il centralise tous les rapports d'exception et, en plus du stacktrace, ajoute l'état de chaque cadre de pile (la valeur des variables au moment où l'exception a été déclenchée). Il fournit également une interface vraiment agréable avec des tableaux de bord, des rapports et des moyens de décomposer les messages par plusieurs projets. Il est open source, vous pouvez donc exécuter votre propre serveur ou vous abonner à la version hébergée..
Certaines défaillances sont temporaires, en particulier lorsqu'il s'agit de systèmes distribués. Un système qui s'effondre au premier signe de difficulté n'est pas très utile.
Si votre code accède à un système distant qui ne répond pas, la solution traditionnelle est le délai d'expiration, mais parfois tous les systèmes ne sont pas conçus avec un délai d'expiration. Les délais d'attente ne sont pas toujours faciles à calibrer lorsque les conditions changent.
Une autre approche consiste à échouer rapidement, puis à réessayer. L'avantage est que si la cible répond rapidement, vous ne passerez pas beaucoup de temps en sommeil et pourrez réagir immédiatement. Mais si cela échoue, vous pouvez réessayer plusieurs fois jusqu'à ce que vous décidiez qu'il est vraiment inaccessible et déclencher une exception. Dans la section suivante, je vais vous présenter un décorateur qui peut le faire pour vous..
Deux décorateurs pouvant aider à la gestion des erreurs sont les suivants: @log_error
, qui enregistre une exception, puis la relance, et le @réessayez
décorateur, qui réessayera d’appeler une fonction plusieurs fois.
Voici une implémentation simple. Le décorateur exclut un objet enregistreur. Quand il décore une fonction et que la fonction est invoquée, elle encapsule l'appel dans une clause try-except, et s'il y a une exception, il l'enregistre et finalement re-soulève l'exception.
def log_error (logger) def created (f): @ functools.wraps (f) def wrapped (* arguments, ** kwargs): essayer: retourne f (* arguments, ** kwargs) sauf Exception comme e: if enregistreur: enregistreur .exception (e) retour retour emballé retour décoré
Voici comment l'utiliser:
logger import logger = logging.getLogger () @log_error (journal) def f (): raise Exception ('Je suis exceptionnel')
Voici une très bonne implémentation du décorateur @retry.
import time import math # Retry décorator avec back-out exponentiel def retry (try, delay = 3, backoff = 2): "Réessaie une fonction ou une méthode jusqu’à ce qu’elle retourne True. delay définit le délai initial en secondes et le paramètre backoff en fonction du facteur le délai doit s'allonger après chaque échec. le délai d'attente doit être supérieur à 1, sinon ce n'est pas vraiment un échec. les tentatives doivent être au moins égales à 0 et le délai supérieur à 0. "si l'échec est atteint <= 1: raise ValueError("backoff must be greater than 1") tries = math.floor(tries) if tries < 0: raise ValueError("tries must be 0 or greater") if delay <= 0: raise ValueError("delay must be greater than 0") def deco_retry(f): def f_retry(*args, **kwargs): mtries, mdelay = tries, delay # make mutable rv = f(*args, **kwargs) # first attempt while mtries > 0: si la variable est True: # Terminé en cas de succès, renvoie True mtries - = 1 # consomme une tentative time.sleep (mdelay) # wait… mdelay * = backoff # fait patienter plus longtemps rv = f (* arguments, ** kwargs) # Essayez à nouveau return False # Répondez aux tentatives :-( return f_retry # vrai décorateur -> fonction décorée return deco_retry # @retry (arg [,…]) -> vrai décorateur
La gestion des erreurs est cruciale pour les utilisateurs et les développeurs. Python fournit un excellent support dans la bibliothèque de langage et standard pour la gestion des erreurs basée sur les exceptions. En suivant les meilleures pratiques avec diligence, vous pouvez vaincre cet aspect souvent négligé.
Apprenez Python avec notre guide complet de tutoriel sur Python, que vous soyez débutant ou que vous soyez un codeur chevronné cherchant à acquérir de nouvelles compétences..