Quelle est la bonne façon de déclarer des classes d'exception personnalisées dans Python moderne? Mon objectif principal est de respecter la norme des autres classes d’exception, afin que, par exemple, toute chaîne supplémentaire que j’inclue dans l’exception soit imprimée par l’outil pris en compte par l’exception.
Par "moderne Python", je veux dire quelque chose qui fonctionnera en Python 2.5 mais sera "correct" pour le Python 2.6 et le Python 3. * façon de faire les choses . Et par "personnalisé", je veux dire un objet Exception pouvant 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.
L'avertissement de désapprobation suivant dans Python 2.6.2 m'a déclenché:
>>> 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
ait une signification particulière pour les attributs nommés message
. D'après ce que je comprends de PEP-352 , cet attribut avait une signification particulière dans la version 2.5, il essayait de le rendre obsolète. Je suppose donc que ce nom (et celui-là seul) est maintenant interdit? Pouah.
Je suis aussi conscient du fait que Exception
a un paramètre magique args
, mais je n’ai jamais su l’utiliser. Je ne suis pas sûr non plus que ce soit la bonne façon de faire les choses; la plupart des discussions que j'ai trouvées en ligne ont suggéré qu'ils essayaient de supprimer les arguments dans Python 3.
Mise à jour: deux réponses ont suggéré de remplacer __init__
et __str__
/__unicode__
/__repr__
. Cela semble beaucoup de dactylographie, est-ce nécessaire?
J'ai peut-être manqué la question, mais pourquoi pas:
class MyException(Exception):
pass
Edit: pour remplacer quelque chose (ou passer des arguments supplémentaires), procédez comme suit:
class ValidationError(Exception):
def __init__(self, message, errors):
# Call the base class constructor with the parameters it needs
super(ValidationError, self).__init__(message)
# Now for your custom code...
self.errors = errors
De cette façon, vous pouvez passer les messages d'erreur au second paramètre et y accéder plus tard avec e.errors
Mise à jour de Python 3: Dans Python 3+, vous pouvez utiliser cette utilisation légèrement plus compacte de super()
:
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
Avec les exceptions Python modernes, vous n'avez pas besoin d'abuser de .message
, ni de remplacer .__str__()
ou .__repr__()
ou aucun de ses éléments. Si tout ce que vous voulez, c'est un message informatif lorsque votre exception est levée, procédez comme suit:
class MyException(Exception):
pass
raise MyException("My hovercraft is full of eels")
Cela donnera une trace se terminant par MyException: My hovercraft is full of eels
.
Si vous souhaitez plus de flexibilité à partir de l'exception, vous pouvez utiliser un dictionnaire comme argument:
raise MyException({"message":"My hovercraft is full of animals", "animal":"eels"})
Cependant, obtenir ces détails dans un bloc except
est un peu plus compliqué. Les détails sont stockés dans l'attribut args
, qui est une liste. Vous devriez faire quelque chose comme ça:
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 passer plusieurs éléments à l'exception et d'y accéder via des index Tuple, mais cela est fortement déconseillé (et était même destiné à la dépréciation il y a longtemps). Si vous avez besoin de plus d’un élément d’information et que la méthode ci-dessus ne vous suffit pas, vous devez alors placer la sous-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
"Moyen approprié de déclarer des exceptions personnalisées dans Python moderne?"
C'est bien, sauf si votre exception est vraiment un type d'exception plus spécifique:
class MyException(Exception):
pass
Ou mieux (peut-être parfait), au lieu de pass
donnez une docstring:
class MyException(Exception):
"""Raise for my specific kind of exception"""
De la docs
Exception
Toutes les exceptions intégrées, ne sortant pas du système, sont dérivées de cette classe. Toutes les exceptions définies par l'utilisateur doivent également être dérivées de cette classe.
Cela signifie que if 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 Si vous dérivez toujours de Exception
comme le recommande la documentation). En outre, vous pouvez au moins fournir une docstring (et ne pas être obligé d'utiliser le mot clé pass
]:
class MyAppValueError(ValueError):
'''Raise when my specific value is wrong'''
Définissez les attributs que vous créez vous-même avec un __init__
personnalisé. Évitez de passer un dict comme argument de position, les futurs utilisateurs de votre code vous en remercieront. Si vous utilisez l'attribut obsolète de message, l'attribuer vous-même évitera un DeprecationWarning
:
class MyAppValueError(ValueError):
'''Raise when a specific subset of values in context of app is wrong'''
def __init__(self, message, foo, *args):
self.message = message # without this you may get DeprecationWarning
# Special attribute you desire with your Error,
# perhaps the value that caused the error?:
self.foo = foo
# allow users initialize misc. arguments as any other builtin Error
super(MyAppValueError, self).__init__(message, foo, *args)
Il n'y a vraiment pas besoin d'écrire votre propre __str__
ou __repr__
. Les composants internes sont très gentils, et votre héritage coopératif garantit que vous l'utilisez.
J'ai peut-être manqué la question, mais pourquoi pas:
class MyException(Exception):
pass
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) ou attraper Exception (mais vous n'êtes probablement pas prêt à gérer tous les types d'exceptions, et vous ne devriez attraper que les exceptions que vous êtes prêt à gérer). Des critiques similaires à celles décrites ci-dessous, mais ce n’est pas non plus le moyen d’initialiser via super
, et vous obtiendrez un DeprecationWarning
si vous accédez à l’attribut du message:
Edit: pour remplacer quelque chose (ou passer des arguments supplémentaires), procédez comme suit:
class ValidationError(Exception):
def __init__(self, message, errors):
# Call the base class constructor with the parameters it needs
super(ValidationError, self).__init__(message)
# Now for your custom code...
self.errors = errors
De cette façon, vous pourrez passer les messages d'erreur au second paramètre et y accéder plus tard avec e.errors
Il faut également exactement deux arguments pour être transmis (à part le self
.) Ni plus, ni moins. C'est une contrainte intéressante que les futurs utilisateurs pourraient ne pas apprécier.
Pour être direct - cela viole substituabilité de Liskov .
Je vais démontrer les deux erreurs:
>>> ValidationError('foo', 'bar', 'baz').message
Traceback (most recent call last):
File "<pyshell#10>", line 1, in <module>
ValidationError('foo', 'bar', 'baz').message
TypeError: __init__() takes exactly 3 arguments (4 given)
>>> ValidationError('foo', 'bar').message
__main__:1: DeprecationWarning: BaseException.message has been deprecated as of Python 2.6
'foo'
Par rapport à:
>>> MyAppValueError('foo', 'FOO', 'bar').message
'foo'
voyez comment les exceptions fonctionnent par défaut si un vs plusieurs attributs sont utilisés (tracebacks omitted):
>>> raise Exception('bad thing happened')
Exception: bad thing happened
>>> raise Exception('bad thing happened', 'code is broken')
Exception: ('bad thing happened', 'code is broken')
vous voudrez peut-être 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 semblable à un tuple, ajoutez simplement la méthode __str__
à la classe ExceptionTemplate
, comme suit:
# ...
def __str__(self):
return ': '.join(self.args)
et vous aurez
>>> raise nastyerr('code is broken')
NastyError: bad thing happened: code is broken
Vous devez remplacer les méthodes __repr__
ou __unicode__
au lieu d'utiliser message. Les arguments que vous fournissez lorsque vous construisez l'exception seront dans l'attribut args
de l'objet exception.
À partir de Python 3.8 (2018, https://docs.python.org/dev/whatsnew/3.8 .html ), la méthode recommandée est toujours:
class CustomExceptionName(Exception):
"""Exception raised when very uncommon things happen"""
pass
N'oubliez pas de documenter pourquoi une exception personnalisée est nécessaire!
Si vous en avez besoin, voici la solution pour les exceptions avec plus de données:
class CustomExceptionName(Exception):
"""Still an exception raised when uncommon things happen"""
def __init__(self, message, payload=None):
self.message = message
self.payload = payload # you could add more args
def __str__(self):
return str(self.message) # __str__() obviously expects a string to be returned, so make sure not to send any other data types
et les chercher comme:
try:
raise CustomExceptionName("Very bad mistake.", "Forgot upgrading from Python 1")
except CustomExceptionName as error:
print(str(error)) # Very bad mistake
print("Detail: {}".format(error.payload)) # Detail: Forgot upgrading from Python 1
payload=None
est important pour le rendre décapable. Avant de le jeter, vous devez appeler error.__reduce__()
. Le chargement fonctionnera comme prévu.
Vous devriez peut-être chercher une solution en utilisant l'instruction pythons return
si vous avez besoin de transférer beaucoup de données vers une structure externe. Cela semble être plus clair/plus pythonique pour moi. Les exceptions avancées sont très utilisées en Java, ce qui peut parfois être ennuyeux lorsque vous utilisez un framework et que vous devez intercepter toutes les erreurs possibles.
Non, le "message" n'est pas interdit. C'est juste obsolète. Votre application fonctionnera bien avec using 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, bon nombre d'entre elles ne sous-classent pas uniquement d'exception, mais d'autres, telles que ValueError ou similaire. Ensuite, vous devez vous adapter à leur utilisation des variables.
Et si votre application comporte de nombreuses exceptions, il est généralement préférable d’avoir une classe de base personnalisée commune pour toutes les applications, afin que les utilisateurs de vos modules puissent le faire.
try:
...
except NelsonsExceptions:
...
Et dans ce cas, vous pouvez effectuer le __init__ and __str__
nécessaire, sans avoir à le répéter à chaque exception. Mais le simple fait d'appeler la variable message autrement que message fait l'affaire.
Dans tous les cas, vous n'avez besoin du __init__ or __str__
que si vous faites quelque chose de différent de ce que fait Exception. 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. ;)
Essayez cet exemple
class InvalidInputError(Exception):
def __init__(self, msg):
self.msg = msg
def __str__(self):
return repr(self.msg)
inp = int(input("Enter a number between 1 to 10:"))
try:
if type(inp) != int or inp not in list(range(1,11)):
raise InvalidInputError
except InvalidInputError:
print("Invalid input entered")
Voir un très bon article " Le guide définitif sur les Python exceptions) ". Les principes de base sont:
BaseException.__init__
avec un seul argument.Il existe également des informations sur l'organisation (dans les modules) et le wrapping des exceptions, je vous recommande de lire le guide.