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?

Était-ce utile?

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.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top