1680 votes

Comment déclarer correctement les exceptions personnalisées dans le langage Python moderne ?

Quelle est la bonne façon de déclarer des classes d'exceptions personnalisées dans le langage Python moderne ? Mon objectif principal est de suivre le standard des autres classes d'exception, de sorte que (par exemple) toute chaîne supplémentaire que j'inclus dans l'exception soit imprimée par l'outil qui a détecté l'exception.

Par "Python moderne", j'entends quelque chose qui fonctionnera dans Python 2.5 mais qui sera "correct" pour la façon de faire de Python 2.6 et Python 3.*. Et par "personnalisé", j'entends un objet Exception qui peut inclure des données supplémentaires sur la cause de l'erreur : une chaîne de caractères, peut-être aussi un autre objet arbitraire pertinent pour l'exception.

J'ai été surpris par l'avertissement de dépréciation suivant dans Python 2.6.2 :

>>> class MyError(Exception):
...     def __init__(self, message):
...         self.message = message
... 
>>> MyError("foo")
_sandbox.py:3: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6

Il semble fou que BaseException a une signification particulière pour les attributs nommés message . J'en déduis que PEP-352 cet attribut avait une signification particulière dans la version 2.5 qu'ils essaient de déprécier, donc je suppose que ce nom (et lui seul) est maintenant interdit ? Ugh.

Je suis aussi vaguement conscient que Exception a un paramètre magique args mais je n'ai jamais su comment l'utiliser. Je ne suis pas non plus sûr que ce soit la bonne façon de faire les choses à l'avenir ; une grande partie des discussions que j'ai trouvées en ligne suggéraient qu'ils essayaient de se débarrasser des args dans Python 3.

Mise à jour : deux réponses ont suggéré de remplacer __init__ y __str__ / __unicode__ / __repr__ . Cela semble être une longue saisie, est-ce nécessaire ?

23 votes

Je crois que c'est l'un de ces cas où Python ne suit pas l'un de ses propres aphorismes : There should be one-- and preferably only one --obvious way to do it.

1741voto

gahooa Points 38006

J'ai peut-être manqué la question, mais pourquoi pas :

class MyException(Exception):
    pass

Pour remplacer quelque chose (ou passer des args supplémentaires), faites ceci :

class ValidationError(Exception):
    def __init__(self, message, errors):            
        # Call the base class constructor with the parameters it needs
        super().__init__(message)

        # Now for your custom code...
        self.errors = errors

De cette façon, vous pouvez passer une dictée de messages d'erreur au deuxième paramètre, et y accéder plus tard avec e.errors .

En Python 2, vous devez utiliser cette forme légèrement plus complexe de super() :

super(ValidationError, self).__init__(message)

59 votes

Cependant, une exception définie de cette manière ne serait pas sélectionnable ; voir la discussion ici. stackoverflow.com/questions/16244923/

157 votes

@jiakai signifie "à picorer". :-)

3 votes

Suivant la documentation de python pour les exceptions définies par l'utilisateur, les noms qui sont mentionnés dans la fonction __init__ sont incorrects. Au lieu de (self,message,error), on trouve (self,expression,message). L'attribut expression est l'expression d'entrée dans laquelle l'erreur s'est produite et le message est une explication de l'erreur.

635voto

frnknstn Points 982

Avec les exceptions modernes de Python, il n'est pas nécessaire d'abuser de la fonction .message ou de passer outre .__str__() o .__repr__() ou tout autre chose. Si tout ce que vous voulez est un message informatif lorsque votre exception est levée, faites ceci :

class MyException(Exception):
    pass

raise MyException("My hovercraft is full of eels")

Cela donnera un retour de trace se terminant par MyException: My hovercraft is full of eels .

Si vous voulez plus de flexibilité de l'exception, vous pouvez passer un dictionnaire comme argument :

raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})

Cependant, pour obtenir ces détails dans un except Le bloc est un peu plus compliqué. Les détails sont stockés dans le args qui est une liste. Vous devriez faire quelque chose comme ceci :

try:
    raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})
except MyException as e:
    details = e.args[0]
    print(details["animal"])

Il est toujours possible de transmettre plusieurs éléments à l'exception et d'y accéder par le biais d'index de tuple, mais il s'agit de fortement déconseillé (et était même destiné à être déprécié il y a quelque temps). Si vous avez besoin de plus d'un élément d'information et que la méthode ci-dessus n'est pas suffisante pour vous, alors vous devriez sous-classer la classe Exception comme décrit dans le tutoriel .

class MyError(Exception):
    def __init__(self, message, animal):
        self.message = message
        self.animal = animal
    def __str__(self):
        return self.message

2 votes

"but this will be deprecated in the future" - est-ce toujours prévu pour la dépréciation ? Python 3.7 semble toujours accepter avec bonheur Exception(foo, bar, qux) .

0 votes

Je n'ai pas vu de travaux récents visant à le déprécariser depuis que la dernière tentative a échoué en raison de la douleur de la transition, mais cet usage est toujours déconseillé. Je vais mettre à jour ma réponse pour refléter cela.

0 votes

@frnknstn, pourquoi est-ce déconseillé ? Il me semble que c'est un bel idiome.

62voto

mykhal Points 5873

Voir comment les exceptions fonctionnent par défaut si un vs plus d'attributs sont utilisés (tracebacks omis) :

>>> raise Exception('bad thing happened')
Exception: bad thing happened

>>> raise Exception('bad thing happened', 'code is broken')
Exception: ('bad thing happened', 'code is broken')

donc vous pourriez vouloir avoir une sorte de " modèle d'exception ", fonctionnant comme une exception elle-même, de manière compatible :

>>> nastyerr = NastyError('bad thing happened')
>>> raise nastyerr
NastyError: bad thing happened

>>> raise nastyerr()
NastyError: bad thing happened

>>> raise nastyerr('code is broken')
NastyError: ('bad thing happened', 'code is broken')

cela peut être fait facilement avec cette sous-classe

class ExceptionTemplate(Exception):
    def __call__(self, *args):
        return self.__class__(*(self.args + args))
# ...
class NastyError(ExceptionTemplate): pass

et si vous n'aimez pas cette représentation par défaut de type tuple, ajoutez simplement __str__ à la méthode ExceptionTemplate classe, comme :

    # ...
    def __str__(self):
        return ': '.join(self.args)

et vous aurez

>>> raise nastyerr('code is broken')
NastyError: bad thing happened: code is broken

19voto

M. Utku ALTINKAYA Points 1549

Vous devez remplacer __repr__ o __unicode__ au lieu d'utiliser le message, les arguments que vous fournissez lorsque vous construisez l'exception se trouveront dans le fichier args de l'objet d'exception.

11voto

Lennart Regebro Points 52510

Non, "message" n'est pas interdit. Il est juste déprécié. Votre application fonctionnera bien si vous utilisez message. Mais vous pouvez vouloir vous débarrasser de l'erreur de dépréciation, bien sûr.

Lorsque vous créez des classes d'exception personnalisées pour votre application, beaucoup d'entre elles ne sont pas seulement des sous-classes d'exception, mais aussi d'autres classes, comme ValueError ou d'autres classes similaires. Vous devez alors vous adapter à leur utilisation des variables.

Et si votre application comporte de nombreuses exceptions, il est généralement judicieux de disposer d'une classe de base personnalisée commune pour chacune d'entre elles, afin que les utilisateurs de vos modules puissent faire ce qui suit

try:
    ...
except NelsonsExceptions:
    ...

Et dans ce cas, vous pouvez faire le __init__ and __str__ nécessaire à cet endroit, afin de ne pas avoir à le répéter pour chaque exception. Mais le simple fait d'appeler la variable message autrement que par message fait l'affaire.

Dans tous les cas, vous n'avez besoin que du __init__ or __str__ si vous faites quelque chose de différent de ce que fait l'Exception elle-même. Et à cause de la dépréciation, vous avez alors besoin des deux, ou vous obtenez une erreur. Ce n'est pas beaucoup de code supplémentaire dont vous avez besoin par classe ;)

1 votes

Il est intéressant de noter que les exceptions de Django n'héritent pas d'une base commune. docs.djangoproject.com/fr/2.2/_modules/django/core/exceptions Avez-vous un bon exemple de cas où il est nécessaire d'attraper toutes les exceptions d'une application spécifique ? (peut-être est-ce utile uniquement pour certains types d'applications spécifiques).

1 votes

J'ai trouvé un bon article sur ce sujet, julien.danjou.info/python-exceptions-guide . Je pense que les exceptions devraient être sous-classées principalement en fonction du domaine, et non de l'application. Lorsque votre application concerne le protocole HTTP, vous dérivez de HTTPError. Quand une partie de votre application est TCP, vous dérivez les exceptions de cette partie de TCPError. Mais si votre application couvre de nombreux domaines (fichiers, autorisations, etc.), la raison d'avoir une MyBaseException diminue. Ou est-ce pour se protéger de la "violation de couche" ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X