nommage - python pep8
Façon correcte de déclarer des exceptions personnalisées dans Python moderne? (4)
Quelle est la bonne façon de déclarer des classes d'exceptions personnalisées dans Python moderne? Mon but principal est de suivre les standards des autres classes d'exception, de sorte que (par exemple) toute chaîne supplémentaire que j'inclue dans l'exception soit imprimée par n'importe quel outil qui a attrapé l'exception.
Par "Python moderne", je veux dire quelque chose qui fonctionnera dans Python 2.5 mais sera "correct" pour Python 2.6 et Python 3. * façon de faire les choses. Et par "custom" je veux dire 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érangé par l'avertissement de désapprobation 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 spéciale pour les attributs nommés message
. D'après le PEP-352 cet attribut a-t-il eu une signification particulière dans la version 2.5, ils essaient de désapprouver, alors je suppose que ce nom (et celui-là seul) est maintenant interdit? Pouah.
Je suis aussi vaguement conscient que Exception
a des arguments de paramètres magiques, mais je n'ai jamais su comment l'utiliser. Je ne suis pas non plus sûr que c'est 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 faire disparaître les arguments dans Python 3.
Mise à jour: deux réponses ont suggéré de surcharger __init__
et __str__
/ __unicode__
/ __repr__
. Cela ressemble à beaucoup de dactylographie, est-ce nécessaire?
"Méthode correcte de déclarer des exceptions personnalisées dans Python moderne?"
C'est très 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
donner une docstring:
class MyException(Exception):
"""Raise for my specific kind of exception"""
Sous-classes d'exception de sous-classes
De la docs
Exception
Toutes les exceptions intégrées qui ne sont pas liées au 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 si votre exception est un type d'une exception plus spécifique, sous-classez cette exception au lieu de l' Exception
générique (et le résultat sera que vous dérivez toujours de l' Exception
comme recommandé par les docs). En outre, vous pouvez au moins fournir une docstring (et ne pas être obligé d'utiliser le mot pass
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 seront reconnaissants. Si vous utilisez l'attribut de message obsolète, l'assigner 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 builtin sont très bien, et votre héritage coopératif vous assure que vous l'utilisez.
Critique de la meilleure réponse
Peut-être que j'ai raté 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 si 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). Une critique similaire à ci-dessous, mais en plus ce n'est pas le moyen d'initialiser via super
, et vous obtiendrez un DeprecationWarning
si vous accédez à l'attribut de message:
Edit: 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(ValidationError, self).__init__(message)
# Now for your custom code...
self.errors = errors
De cette façon, vous pourriez transmettre des dicts de messages d'erreur au second param, et y accéder plus tard avec e.errors
Il faut aussi que deux arguments soient passés (en dehors de self
- self
). Pas plus, pas moins. C'est une contrainte intéressante que les futurs utilisateurs pourraient ne pas apprécier.
Pour être direct - il viole la 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'
Avec les exceptions Python modernes, vous n'avez pas besoin d'abuser de .message
, ou de surcharger .__str__()
ou .__repr__()
ou n'importe lequel d'entre eux. Si tout ce que vous voulez est un message informatif lorsque votre exception est soulevée, faites ceci:
class MyException(Exception):
pass
raise MyException("My hovercraft is full of eels")
Cela donnera une MyException: My hovercraft is full of eels
terminant par MyException: My hovercraft is full of eels
.
Si vous voulez plus de flexibilité, 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 except
est un peu plus compliqué. Ils sont stockés dans l'attribut args
, qui est une liste. Vous auriez besoin de 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 transmettre plusieurs éléments dans l'exception, mais cela sera abandonné dans le futur. Si vous avez besoin de plus d'une seule information, vous devriez envisager de sous- Exception
.
Peut-être que j'ai raté la question, mais pourquoi pas:
class MyException(Exception):
pass
Edit: 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(ValidationError, self).__init__(message)
# Now for your custom code...
self.errors = errors
De cette façon, vous pourriez transmettre des dicts de messages d'erreur au second param, et y accéder plus tard avec e.errors
Mise à jour de Python 3: En 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
Vous devez substituer les méthodes __repr__
ou __unicode__
au lieu d'utiliser le message, les arguments que vous fournissez lorsque vous construisez l'exception seront dans l'attribut args
de l'objet exception.