Quelle est la bonne façon de déclarer des classes d'exceptions personnalisées en Python moderne? Mon objectif principal est de suivre ce que les autres classes d'exceptions standard ont, 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», je veux dire quelque chose qui fonctionnera en Python 2.5 mais qui sera «correct» pour la façon de faire 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, peut-être aussi un autre objet arbitraire pertinent pour l'exception.
J'ai été déclenché 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
Cela semble fou qui BaseException
a une signification particulière pour les attributs nommés message
. J'en déduis du PEP-352 que cet attribut avait une signification spéciale dans 2.5 qu'ils essaient de déprécier, donc je suppose que ce nom (et celui-là seul) est maintenant interdit? Pouah.
Je suis également vaguement conscient de l'existence Exception
d'un paramètre magique args
, mais je n'ai jamais su comment l'utiliser. Je ne suis pas sûr non plus que ce soit la bonne façon de faire les choses à l'avenir; une grande partie de la discussion que j'ai trouvée en ligne a suggéré qu'ils essayaient de supprimer les arguments en Python 3.
Mise à jour: deux réponses ont suggéré de remplacer __init__
, et __str__
/ __unicode__
/ __repr__
. Cela semble beaucoup taper, est-ce nécessaire?
Avec des exceptions Python modernes, vous n'avez pas besoin d'abus
.message
, ou override.__str__()
ou.__repr__()
ou l' un de. Si tout ce que vous voulez est un message informatif lorsque votre exception est levée, procédez comme suit:Cela donnera un traceback 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:
Cependant, obtenir ces détails dans un
except
bloc est un peu plus compliqué. Les détails sont stockés dans l'args
attribut, qui est une liste. Vous auriez besoin de faire quelque chose comme ceci:Il est toujours possible de passer plusieurs éléments à l'exception et d'y accéder via des index de tuple, mais cela est fortement déconseillé (et était même destiné à la dépréciation il y a quelque temps). Si vous avez besoin de plus d'une seule information et que la méthode ci-dessus ne vous suffit pas, vous devez sous-classer
Exception
comme décrit dans le didacticiel .la source
Exception(foo, bar, qux)
.C'est très bien, sauf si votre exception est vraiment un type d'exception plus spécifique:
Ou mieux (peut-être parfait), au lieu de
pass
donner une docstring:Sous-classes d'exception
À partir des documents
Cela signifie que si votre exception est un type d'exception plus spécifique, sous-classe cette exception au lieu du générique
Exception
(et le résultat sera que vous dérivez toujoursException
comme le recommandent les documents). De plus, vous pouvez au moins fournir une docstring (et ne pas être obligé d'utiliser lepass
mot - clé):Définissez les attributs que vous créez vous-même avec une personnalisation
__init__
. Évitez de passer un dict comme argument positionnel, les futurs utilisateurs de votre code vous en remercieront. Si vous utilisez l'attribut de message obsolète, son attribution vous-même éviteraDeprecationWarning
:Il n'y a vraiment pas besoin d'écrire votre propre
__str__
ou__repr__
. Ceux intégrés sont très agréables, et votre héritage coopératif garantit que vous l'utilisez.Critique de la meilleure réponse
Encore une fois, le problème avec ce qui précède est que pour l'attraper, vous devrez soit le nommer spécifiquement (l'importer s'il est créé ailleurs), soit intercepter l'exception, (mais vous n'êtes probablement pas prêt à gérer tous les types d'exceptions, et vous ne devez intercepter que les exceptions que vous êtes prêt à gérer). Critique similaire à ce qui suit, mais en plus, ce n'est pas la façon d'initialiser via
super
, et vous obtiendrez unDeprecationWarning
si vous accédez à l'attribut de message:Il faut également que deux arguments soient transmis (à part le
self
.) Pas plus, pas moins. C'est une contrainte intéressante que les futurs utilisateurs pourraient ne pas apprécier.Pour être direct - cela viole la substituabilité de Liskov .
Je vais démontrer les deux erreurs:
Par rapport à:
la source
BaseException.message
est parti en Python 3, donc la critique ne vaut que pour les anciennes versions, non?__str__
méthodeMyAppValueError
au lieu de s'appuyer sur l'message
attributValueError
. Cela a du sens si c'est dans la catégorie des erreurs de valeur. Si ce n'est pas dans la catégorie des erreurs de valeur, je m'y opposerais sur la sémantique. Il y a de la place pour certaines nuances et raisonnements de la part du programmeur, mais je préfère de loin la spécificité, le cas échéant. Je mettrai à jour ma réponse pour mieux aborder le sujet très prochainement.voir comment les exceptions fonctionnent par défaut si un ou plusieurs attributs sont utilisés (tracebacks omis):
donc vous voudrez peut-être avoir une sorte de " modèle d'exception ", fonctionnant comme une exception elle-même, d'une manière compatible:
cela peut être fait facilement avec cette sous-classe
et si vous n'aimez pas cette représentation par défaut comme un tuple, ajoutez simplement une
__str__
méthode à laExceptionTemplate
classe, comme:et vous aurez
la source
Depuis Python 3.8 (2018, https://docs.python.org/dev/whatsnew/3.8.html ), la méthode recommandée est toujours:
N'oubliez pas de documenter, pourquoi une exception personnalisée est nécessaire!
Si vous en avez besoin, voici la voie à suivre pour les exceptions avec plus de données:
et les récupérer comme:
payload=None
est important pour le rendre décapant. Avant de le vider, vous devez appelererror.__reduce__()
. Le chargement fonctionnera comme prévu.Vous devriez peut-être chercher à trouver une solution à l'aide de l'
return
instruction pythons si vous avez besoin de beaucoup de données à transférer vers une structure externe. Cela me semble plus clair / plus pythonique. Les exceptions avancées sont largement utilisées en Java, ce qui peut parfois être ennuyeux, lorsque vous utilisez un framework et que vous devez intercepter toutes les erreurs possibles.la source
__str__
) plutôt que d'autres réponses qui utilisentsuper().__init__(...)
.. Juste une honte qui remplace__str__
et__repr__
est probablement nécessaire juste pour une meilleure sérialisation "par défaut".Vous devez remplacer
__repr__
ou__unicode__
méthodes au lieu d'utiliser message, les arguments que vous fournissez lorsque vous construisez l'exception seront dans l'args
attribut de l'objet d'exception.la source
Non, le "message" n'est pas interdit. C'est juste obsolète. Votre application fonctionnera bien avec l'utilisation du message. Mais vous voudrez peut-être 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 sous-classent pas uniquement à partir d'Exception, mais à partir d'autres, comme ValueError ou similaire. Ensuite, vous devez vous adapter à leur utilisation des variables.
Et si vous avez de nombreuses exceptions dans votre application, c'est généralement une bonne idée d'avoir une classe de base personnalisée commune pour toutes, afin que les utilisateurs de vos modules puissent faire
Et dans ce cas, vous pouvez faire le
__init__ and __str__
nécessaire là-bas, vous n'avez donc pas à le répéter pour chaque exception. Mais simplement appeler la variable message autre chose que message fait l'affaire.Dans tous les cas, vous n'avez besoin que
__init__ or __str__
si vous faites quelque chose de différent de ce que fait l'Exception elle-même. Et parce que si 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. ;)la source
Voir un très bon article " Le guide définitif des exceptions Python ". Les principes de base sont les suivants:
BaseException.__init__
avec un seul argument.Il y a aussi des informations sur l'organisation (en modules) et le wrapping des exceptions, je recommande de lire le guide.
la source
Always call BaseException.__init__ with only one argument.
Semble comme une contrainte inutile, car elle accepte en fait un nombre illimité d'arguments.Essayez cet exemple
la source
Pour définir correctement vos propres exceptions, vous devez suivre quelques bonnes pratiques:
Définissez une classe de base héritant de
Exception
. Cela permettra d'attraper toutes les exceptions liées au projet (des exceptions plus spécifiques devraient en hériter):Organiser ces classes d'exception dans un module séparé (par exemple
exceptions.py
) est généralement une bonne idée.Pour créer une exception personnalisée, sous-classe la classe d'exception de base.
Pour ajouter la prise en charge des arguments supplémentaires à une exception personnalisée, définissez une
__init__()
méthode personnalisée avec un nombre variable d'arguments. Appelez la classe de base en lui__init__()
passant tous les arguments positionnels (rappelez-vous queBaseException
/Exception
attendez - vous à n'importe quel nombre d' arguments positionnels ):Pour déclencher une telle exception avec un argument supplémentaire, vous pouvez utiliser:
Cette conception respecte le principe de substitution Liskov , car vous pouvez remplacer une instance d'une classe d'exception de base par une instance d'une classe d'exception dérivée. En outre, il vous permet de créer une instance d'une classe dérivée avec les mêmes paramètres que le parent.
la source