Une bonne façon de déclarer des exceptions personnalisées en Python moderne?
-
19-09-2019 - |
Question
Quelle est la bonne façon de déclarer des classes d'exception personnalisées en Python moderne? Mon objectif principal est de suivre d'autres classes Quelque soit le modèle d'exception ont, de sorte que (par exemple) une chaîne supplémentaire que je comprends dans l'exception est imprimé quel que soit l'outil pris l'exception.
Par « Python moderne » Je veux dire quelque chose qui va fonctionner en Python 2.5, mais être « correct » pour la façon dont Python 2.6 et Python 3. * de faire les choses. Et par « coutume » Je veux dire un objet d'exception qui peut inclure des données supplémentaires sur la cause de l'erreur:. Une chaîne, peut-être un autre objet arbitraire pertinentes à l'exception
je trébuché par l'avertissement suivant deprecation en 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
. Je déduis de PEP-352 cet attribut a eu une signification particulière dans 2,5 ils essayons de désapprouver loin, donc je suppose que ce nom (et que l'on seul) est maintenant interdit? Ugh.
Je suis aussi conscient que Exception
flou a un paramètre magique args
, mais je ne l'ai jamais su comment l'utiliser. Je ne suis pas sûr que c'est la bonne façon de faire les choses à l'avenir; beaucoup de la discussion, j'ai trouvé en ligne a suggéré qu'ils essayaient de faire disparaître args en Python 3.
Mise à jour: deux réponses ont suggéré __init__
supérieur, et __str__
/ __unicode__
/ __repr__
. Cela semble beaucoup de frappe, est-il nécessaire?
La solution
Peut-être que j'ai raté la question, mais pourquoi pas:
class MyException(Exception):
pass
Edit: pour remplacer quelque chose (ou passer args 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 dict des messages d'erreur au second param, et y accéder plus tard avec e.errors
Python 3 Mise à jour: En Python 3+, vous pouvez utiliser cette utilisation un peu plus compact 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
Autres conseils
Avec des exceptions Python modernes, vous n'avez pas besoin d'abuser .message
, ou remplacer .__str__()
ou .__repr__()
ou l'un de. Si vous voulez simplement 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 retraçage 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 bloc de except
est un peu plus compliqué. Les détails sont stockés dans l'attribut args
, qui est une liste. Vous devez 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 encore possible de transmettre plusieurs articles à l'exception et y accéder via des index de tuple, mais cela est très découragée (et a même été destiné à deprecation un certain temps). Si vous avez besoin de plus d'un seul élément d'information et la méthode ci-dessus ne suffit pas pour vous, alors vous devez sous-classe Exception
comme décrit dans la section tutoriel .
class MyError(Exception):
def __init__(self, message, animal):
self.message = message
self.animal = animal
def __str__(self):
return self.message
"Une bonne façon de déclarer des exceptions personnalisées en Python moderne?"
Ceci est bien, à moins que votre exception est vraiment un type d'une exception plus spécifique:
class MyException(Exception):
pass
Ou mieux (peut-être parfait), au lieu de donner un pass
docstring:
class MyException(Exception):
"""Raise for my specific kind of exception"""
Subclassing Exception Sous-classes
De la docs
Exception
Toutes intégré, des exceptions non-sortie du système sont dérivées de cette classe. Toutes les exceptions définies par l'utilisateur doivent également être dérivés de cette classe.
Cela signifie que si votre exception est un type d'une exception plus spécifique, sous-classe exception au lieu du Exception
générique (et le résultat sera que vous dérivez encore de Exception
que les docs recommandent) . En outre, vous pouvez au moins fournir un docstring (et ne pas être forcé d'utiliser le mot-clé pass
):
class MyAppValueError(ValueError):
'''Raise when my specific value is wrong'''
Définir vous attribue créer vous-même avec un __init__
personnalisé. Éviter de passer un dict comme argument de position, futurs utilisateurs de votre code vous remercie. Si vous utilisez l'attribut de message dépréciée, lui attribuant vous é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__
. Ceux builtin sont très gentils, et héritage coopératif assure que vous l'utilisez.
Critique de la réponse top
Peut-être que je pas compris 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 devez soit le nommer spécifiquement (importer si créé ailleurs) ou exception de capture, (mais vous n'êtes probablement pas prêt à gérer tous les types des exceptions, et vous ne devez prendre des exceptions que vous êtes prêt à gérer). Des critiques similaires au-dessous, mais en plus ce n'est pas la façon d'initialiser via super
, et vous aurez un DeprecationWarning
si vous accédez à l'attribut de message:
Edit: pour remplacer quelque chose (ou passe args supplémentaires), faites ceci:
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 dict des messages d'erreur au second param, et y accéder plus tard avec e.errors
Il faut aussi exactement deux arguments passés dans (en dehors de la self
.) Pas plus, pas moins. C'est une contrainte intéressante que les futurs utilisateurs ne peuvent pas apprécier.
Pour être direct -. Viole Liskov substituabilité
Je démontrerai 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'
voir comment les exceptions fonctionnent par défaut si un vs plusieurs 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 voudrez peut-être avoir une sorte de " modèle d'exception ", travaillant comme une exception elle-même, d'une manière compatible avec:
>>> 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 se faire 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 ne le faites pas comme cette représentation comme tuple par défaut, ajoutez juste méthode __str__
à la classe ExceptionTemplate
, comme:
# ...
def __str__(self):
return ': '.join(self.args)
et vous avez
>>> raise nastyerr('code is broken')
NastyError: bad thing happened: code is broken
Vous devez remplacer les méthodes de __repr__
ou __unicode__
au lieu d'utiliser un message, les args que vous fournissez lorsque vous construisez l'exception sera dans l'attribut args
de l'objet d'exception.
As of Python 3.8 (2018, https://docs.python.org/dev/whatsnew/3.8.html), the recommended method is still:
class CustomExceptionName(Exception):
"""Exception raised when very uncommon things happen"""
pass
Please don't forget to document, why a custom exception is neccessary!
If you need to, this is the way to go for exceptions with more data:
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
and fetch them like:
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
is important to make it pickle-able. Before dumping it, you have to call error.__reduce__()
. Loading will work as expected.
You maybe should investigate in finding a solution using pythons return
statement if you need much data to be transferred to some outer structure. This seems to be clearer/more pythonic to me. Advanced exceptions are heavily used in Java, which can sometimes be annoying, when using a framework and having to catch all possible errors.
No, "message" is not forbidden. It's just deprecated. You application will work fine with using message. But you may want to get rid of the deprecation error, of course.
When you create custom Exception classes for your application, many of them do not subclass just from Exception, but from others, like ValueError or similar. Then you have to adapt to their usage of variables.
And if you have many exceptions in your application it's usually a good idea to have a common custom base class for all of them, so that users of your modules can do
try:
...
except NelsonsExceptions:
...
And in that case you can do the __init__ and __str__
needed there, so you don't have to repeat it for every exception. But simply calling the message variable something else than message does the trick.
In any case, you only need the __init__ or __str__
if you do something different from what Exception itself does. And because if the deprecation, you then need both, or you get an error. That's not a whole lot of extra code you need per class. ;)
Try this Example
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")
See a very good article "The definitive guide to Python exceptions". The basic principles are:
- Always inherit from (at least) Exception.
- Always call
BaseException.__init__
with only one argument. - When building a library, define a base class inheriting from Exception.
- Provide details about the error.
- Inherit from builtin exceptions types when it makes sense.
There is also information on organizing (in modules) and wrapping exceptions, I recommend to read the guide.