Question Façon correcte de déclarer des exceptions personnalisées dans Python moderne?


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 particulière pour les attributs nommés message. Je comprends de PEP-352 cet attribut avait une signification particulière dans 2.5 ils essayent de déprécier loin, donc je suppose que ce nom (et celui-là seul) est maintenant interdit? Pouah.

Je suis aussi vaguement conscient que Exception a un paramètre magique argsmais je n'ai jamais su 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 déroger __init__, et __str__/__unicode__/__repr__. Cela ressemble à beaucoup de dactylographie, est-ce nécessaire?


892
2017-08-23 21:29


origine


Réponses:


Peut-être que j'ai raté la question, mais pourquoi pas:

class MyException(Exception):
    pass

Modifier: 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 arriver plus tard avec e.errors


Mise à jour de Python 3: En Python 3+, vous pouvez utiliser cette utilisation légèrement plus compacte 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

924
2017-08-23 21:55



Avec les exceptions Python modernes, vous n'avez pas besoin d'abuser .messageou remplacer .__str__() ou .__repr__() ou tout cela. 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 un retraçage avec 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 bloc est un peu plus compliqué. Les détails sont stockés dans le args attribut, 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 passer plusieurs éléments à l'exception et d'y accéder via des index de tuple, mais ceci est fortement découragé (Et était même destiné à la dépréciation un certain temps). Si vous avez besoin de plus d'une seule information et que la méthode ci-dessus n'est pas suffisante pour vous, alors vous devriez sous-classer Exception comme décrit dans le Didacticiel.

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

359
2018-04-22 18:18



"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

Du 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-classe cette exception au lieu du générique Exception (et le résultat sera que vous dérivez encore de Exception comme les docs le recommandent). En outre, vous pouvez au moins fournir un docstring (et ne pas être forcé d'utiliser le pass mot-clé):

class MyAppValueError(ValueError):
    '''Raise when my specific value is wrong'''

Définir les attributs que vous créez vous-même avec une coutume __init__. É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 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 le vôtre __str__ ou __repr__. Les builtin sont très agréables, et votre héritage coopératif veille à ce que vous l'utilisiez.

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). Critique similaire à la ci-dessous, mais en outre ce n'est pas le moyen d'initialiser via superet vous aurez 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 (hormis les self.) Ni plus ni 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'

150
2017-11-14 21:09



voir comment les exceptions fonctionnent par défaut si un contre plus d'attributs sont utilisés (retraçage 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", fonctionnant comme une exception, d'une 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, il suffit d'ajouter __str__ méthode à la 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

37
2017-08-07 16:23



Vous devez remplacer __repr__ ou __unicode__ Au lieu d'utiliser un message, les arguments que vous fournissez lorsque vous construisez l'exception seront dans le args attribut de l'objet d'exception.


14
2017-08-23 21:46



Non, "message" n'est pas interdit. C'est juste déprécié. Votre application fonctionnera bien avec l'utilisation du 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'exceptions personnalisées pour votre application, la plupart d'entre elles ne sont pas des sous-classes provenant uniquement d'exceptions, mais 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 chacun d'entre eux, afin que les utilisateurs de vos modules puissent faire

try:
    ...
except NelsonsExceptions:
    ...

Et dans ce cas, vous pouvez faire le __init__ and __str__ besoin là, donc vous n'avez pas à le répéter pour chaque exception. Mais simplement appeler la variable de message quelque chose d'autre que le message fait l'affaire.

En tout cas, vous avez seulement besoin de la __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 besoin des deux, ou vous obtenez une erreur. Ce n'est pas beaucoup de code supplémentaire dont vous avez besoin par classe. ;)


5
2017-08-23 21:58



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")

0
2017-07-22 05:27