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.





exception