Comment lever la même exception avec un message personnalisé en Python?

146

J'ai ce trybloc dans mon code:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise ValueError(errmsg)

À proprement parler, j'en soulève un autre ValueError , pas le ValueErrorjeté par do_something...(), qui est appelé errdans ce cas. Comment puis-je joindre un message personnalisé à err? J'essaie le code suivant mais échoue en raison d' errune ValueError instance qui ne peut pas être appelée:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise err(errmsg)
Trousse
la source
13
@Hamish, joindre des informations supplémentaires et relancer des exceptions peut être très utile lors du débogage.
Johan Lundberg
@Johan Absolument - et c'est à cela que sert un stacktrace. Je ne comprends pas très bien pourquoi vous modifiez le message d'erreur existant au lieu de générer une nouvelle erreur.
Hamish
@Hamish. Bien sûr, mais vous pouvez ajouter d'autres éléments. Pour votre question, regardez ma réponse et l'exemple de UnicodeDecodeError. Si vous avez des commentaires à ce sujet, commentez peut-être ma réponse.
Johan Lundberg
Possibilité de dupliquer Ajouter des informations à une exception?
Trevor Boyd Smith
1
@Kit c'est 2020 et python 3 est partout. Pourquoi ne pas changer la réponse acceptée à la réponse de Ben :-)
mit

Réponses:

87

Mise à jour: pour Python 3, vérifiez la réponse de Ben


Pour attacher un message à l'exception actuelle et la relancer: (le try / except externe est juste pour montrer l'effet)

Pour python 2.x où x> = 6:

try:
    try:
      raise ValueError  # something bad...
    except ValueError as err:
      err.message=err.message+" hello"
      raise              # re-raise current exception
except ValueError as e:
    print(" got error of type "+ str(type(e))+" with message " +e.message)

Cela fera également la bonne chose si errest dérivé de ValueError. Par exemple UnicodeDecodeError.

Notez que vous pouvez ajouter ce que vous voulez err. Par exemple err.problematic_array=[1,2,3].


Edit: @Ducan indique dans un commentaire que ce qui précède ne fonctionne pas avec python 3 car il .messagen'est pas membre de ValueError. À la place, vous pouvez utiliser ceci (valide python 2.6 ou version ultérieure ou 3.x):

try:
    try:
      raise ValueError
    except ValueError as err:
       if not err.args: 
           err.args=('',)
       err.args = err.args + ("hello",)
       raise 
except ValueError as e:
    print(" error was "+ str(type(e))+str(e.args))

Edit2:

En fonction de l'objectif, vous pouvez également opter pour l'ajout d'informations supplémentaires sous votre propre nom de variable. Pour python2 et python3:

try:
    try:
      raise ValueError
    except ValueError as err:
       err.extra_info = "hello"
       raise 
except ValueError as e:
    print(" error was "+ str(type(e))+str(e))
    if 'extra_info' in dir(e):
       print e.extra_info
Johan Lundberg
la source
9
Puisque vous vous êtes efforcé d'utiliser la gestion des exceptions de style Python 3 et print, vous devriez probablement noter que votre code ne fonctionne pas dans Python 3.x car il n'y a pas d' messageattribut sur les exceptions. err.args = (err.args[0] + " hello",) + err.args[1:]peut fonctionner de manière plus fiable (et ensuite simplement convertir en une chaîne pour obtenir le message).
Duncan
1
Malheureusement, il n'y a aucune garantie que args [0] est un type de chaîne représentant un message d'erreur - "Le tuple d'arguments donné au constructeur d'exceptions. Certaines exceptions intégrées (comme IOError) attendent un certain nombre d'arguments et attribuent une signification spéciale à les éléments de ce tuple, tandis que les autres sont généralement appelés uniquement avec une seule chaîne donnant un message d'erreur. ". Donc, le code ne fonctionnera pas arg [0] n'est pas un message d'erreur (il peut s'agir d'un int, ou d'une chaîne représentant un nom de fichier).
Trent
1
@Taras, intéressant. Avez-vous une référence à ce sujet? Ensuite, j'ajouterais à un tout nouveau membre: err.my_own_extra_info. Ou encapsulez tout cela dans ma propre exception en conservant les informations nouvelles et originales.
Johan Lundberg
2
Un exemple réel de quand args [0] n'est pas un message d'erreur - docs.python.org/2/library/exceptions.html - "exception EnvironmentError La classe de base pour les exceptions qui peuvent se produire en dehors du système Python: IOError, OSError. Lorsque des exceptions de ce type sont créées avec un 2-tuple, le premier élément est disponible sur l'attribut errno de l'instance (il est supposé être un numéro d'erreur), et le deuxième élément est disponible sur l'attribut strerror (il s'agit généralement de l'attribut message d'erreur). Le tuple lui-même est également disponible sur l'attribut args. "
Trent
2
Je ne comprends pas du tout cela. La seule raison pour laquelle la définition de l' .messageattribut fait quelque chose ici est que cet attribut est explicitement imprimé. Si vous deviez lever l'exception sans attraper et imprimer, vous ne verriez pas l' .messageattribut faire quoi que ce soit d'utile.
DanielSank
170

Si vous avez la chance de ne supporter que python 3.x, cela devient vraiment une chose de beauté :)

augmenter de

Nous pouvons enchaîner les exceptions en utilisant relancer de .

try:
    1 / 0
except ZeroDivisionError as e:
    raise Exception('Smelly socks') from e

Dans ce cas, l'exception que votre appelant attraperait a le numéro de ligne de l'endroit où nous levons notre exception.

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    raise Exception('Smelly socks') from e
Exception: Smelly socks

Notez que l'exception du bas n'a que le stacktrace à partir duquel nous avons levé notre exception. Votre appelant peut toujours obtenir l'exception d'origine en accédant à l' __cause__attribut de l'exception qu'il détecte.

with_traceback

Ou vous pouvez utiliser with_traceback .

try:
    1 / 0
except ZeroDivisionError as e:
    raise Exception('Smelly socks').with_traceback(e.__traceback__)

En utilisant ce formulaire, l'exception que votre appelant attraperait a le traçage à partir de l'endroit où l'erreur d'origine s'est produite.

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    raise Exception('Smelly socks').with_traceback(e.__traceback__)
  File "test.py", line 2, in <module>
    1 / 0
Exception: Smelly socks

Notez que l'exception du bas a la ligne où nous avons effectué la division invalide ainsi que la ligne où nous relançons l'exception.

Ben
la source
2
Est-il possible d'ajouter un message personnalisé à une exception sans le traçage supplémentaire? Par exemple, peut raise Exception('Smelly socks') from eêtre modifié pour ajouter simplement "Smelly socks" en tant que commentaire au retraçage d'origine plutôt que d'introduire un nouveau retraçage de son propre chef.
joelostblom
C'est le comportement que vous obtiendrez de la réponse de Johan Lundberg
Ben
3
c'est vraiment adorable. Je vous remercie.
allanberry
4
Relancer une nouvelle exception ou une chaîne en soulevant des exceptions avec de nouveaux messages crée plus de confusion que nécessaire dans de nombreux cas. En soi, les exceptions sont complexes à gérer. Une meilleure stratégie consiste simplement à ajouter votre message à l'argument de l'exception d'origine si possible comme dans err.args + = ("message",) et à relancer le message d'exception. Le traçage peut ne pas vous emmener aux numéros de ligne où l'exception a été interceptée, mais il vous mènera à l'endroit où l'exception s'est produite à coup sûr.
user-asterix
2
Vous pouvez également supprimer explicitement l' affichage de la chaîne d'exceptions en spécifiant None dans la clause from:raise RuntimeError("Something bad happened") from None
pfabri
9
try:
    try:
        int('a')
    except ValueError as e:
        raise ValueError('There is a problem: {0}'.format(e))
except ValueError as err:
    print err

imprime:

There is a problem: invalid literal for int() with base 10: 'a'
eumiro
la source
1
Je me demandais s'il y avait un idiome Python pour ce que j'essaie de faire, autre que de créer une autre instance.
Kit du
@Kit - J'appellerais cela `` ré-lever une exception '': docs.python.org/reference/simple_stmts.html#raise
eumiro
1
@eumiro, Non, vous faites une nouvelle exception. Voyez ma réponse. À partir de votre lien: "... mais augmenter sans expression doit être préféré si l'exception à relancer était l'exception la plus récemment active dans la portée actuelle."
Johan Lundberg
3
@JohanLundberg - raisesans paramètres est re-soulevé. Si OP veut ajouter un message, il doit lever une nouvelle exception et peut réutiliser le message / type de l'exception d'origine.
eumiro
2
Si vous souhaitez ajouter un message, vous ne pouvez pas créer un nouveau message à partir de zéro en lançant "ValueError". Ce faisant, vous détruisez les informations sous-jacentes de quel type de ValueError il s'agit (similaire au découpage en C ++). En relançant la même exception avec augmentation sans argument, vous passez l'objet d'origine avec ce type spécifique correct (dérivé de ValueError).
Johan Lundberg
9

Il semble que toutes les réponses ajoutent des informations à e.args [0], modifiant ainsi le message d'erreur existant. Y a-t-il un inconvénient à étendre le tuple args à la place? Je pense que l'avantage possible est que vous pouvez laisser le message d'erreur d'origine seul pour les cas où l'analyse de cette chaîne est nécessaire; et vous pouvez ajouter plusieurs éléments au tuple si votre gestion d'erreur personnalisée produit plusieurs messages ou codes d'erreur, pour les cas où le traçage serait analysé par programme (comme via un outil de surveillance du système).

## Approach #1, if the exception may not be derived from Exception and well-behaved:

def to_int(x):
    try:
        return int(x)
    except Exception as e:
        e.args = (e.args if e.args else tuple()) + ('Custom message',)
        raise

>>> to_int('12')
12

>>> to_int('12 monkeys')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')

ou

## Approach #2, if the exception is always derived from Exception and well-behaved:

def to_int(x):
    try:
        return int(x)
    except Exception as e:
        e.args += ('Custom message',)
        raise

>>> to_int('12')
12

>>> to_int('12 monkeys')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')

Pouvez-vous voir un inconvénient à cette approche?

Chris Johnson
la source
Mon ancienne réponse ne modifie pas e.args [0].
Johan Lundberg
4

C'est la fonction que j'utilise pour modifier le message d'exception dans Python 2.7 et 3.x tout en préservant le traçage d'origine. Cela demandesix

def reraise_modify(caught_exc, append_msg, prepend=False):
    """Append message to exception while preserving attributes.

    Preserves exception class, and exception traceback.

    Note:
        This function needs to be called inside an except because
        `sys.exc_info()` requires the exception context.

    Args:
        caught_exc(Exception): The caught exception object
        append_msg(str): The message to append to the caught exception
        prepend(bool): If True prepend the message to args instead of appending

    Returns:
        None

    Side Effects:
        Re-raises the exception with the preserved data / trace but
        modified message
    """
    ExceptClass = type(caught_exc)
    # Keep old traceback
    traceback = sys.exc_info()[2]
    if not caught_exc.args:
        # If no args, create our own tuple
        arg_list = [append_msg]
    else:
        # Take the last arg
        # If it is a string
        # append your message.
        # Otherwise append it to the
        # arg list(Not as pretty)
        arg_list = list(caught_exc.args[:-1])
        last_arg = caught_exc.args[-1]
        if isinstance(last_arg, str):
            if prepend:
                arg_list.append(append_msg + last_arg)
            else:
                arg_list.append(last_arg + append_msg)
        else:
            arg_list += [last_arg, append_msg]
    caught_exc.args = tuple(arg_list)
    six.reraise(ExceptClass,
                caught_exc,
                traceback)
Bryce Guinta
la source
C'est l'une des rares réponses qui répond réellement à la question initiale. Alors, c'est bien. Malheureusement, Python 2.7 (et donc six) est EOL. Alors, c'est mauvais. Bien que techniquement on puisse encore l'utiliser sixen 2020, il n'y a pas d'avantage tangible à le faire. Les solutions Pure-Python 3.x sont désormais largement préférables.
Cecil Curry
3

Ce modèle de code doit vous permettre de déclencher une exception avec un message personnalisé.

try:
     raise ValueError
except ValueError as err:
    raise type(err)("my message")
Ruggero Turra
la source
4
Cela ne préserve pas la trace de la pile.
plok
Le questionneur ne spécifie pas que la trace de pile soit préservée.
shrewmouse
Ne soyez pas intentionnellement obtus. La question originale textuelle était: "Comment puis-je soulever la même chose Exceptionavec un message personnalisé en Python?" Cette non-réponse soulève une nouvelle exception et ne répond donc pas du tout à la question initiale. C'est pourquoi nous ne pouvons pas avoir de bonnes choses.
Cecil Curry
3

Soit déclencher la nouvelle exception avec votre message d'erreur en utilisant

raise Exception('your error message')

ou

raise ValueError('your error message')

à l'endroit où vous voulez le lever OU joindre (remplacer) le message d'erreur dans l'exception actuelle en utilisant 'from' (Python 3.x pris en charge uniquement):

except ValueError as e:
  raise ValueError('your message') from e
Alexey Antonenko
la source
Thanx, @gberger, l'approche 'from e' n'est en fait pas prise en charge par python 2.x
Alexey Antonenko
À la surprise de personne, cela ne répond pas à la question réelle.
Cecil Curry
2

La réponse actuelle n'a pas fonctionné correctement pour moi, si l'exception n'est pas réattrapée, le message ajouté n'est pas affiché.

Mais faire comme ci-dessous permet de conserver la trace et d'afficher le message ajouté, que l'exception soit à nouveau interceptée ou non.

try:
  raise ValueError("Original message")
except ValueError as err:
  t, v, tb = sys.exc_info()
  raise t, ValueError(err.message + " Appended Info"), tb

(J'ai utilisé Python 2.7, je ne l'ai pas essayé en Python 3)

Zitrax
la source
2

Les exceptions intégrées Python 3 ont le strerrorchamp:

except ValueError as err:
  err.strerror = "New error message"
  raise err
user3761308
la source
Cela ne semble pas fonctionner. Vous manquez quelque chose?
MasayoMusic
La strerrorvariable d'instance est spécifique à l' OSErrorexception . Étant donné que la plupart des ValueErrorexceptions sont garanties de ne pas définir cette variable, cette non-solution soulève généralement des exceptions non lisibles par l'homme et est donc activement nuisible. lol, bro.
Cecil Curry
1

Cela ne fonctionne qu'avec Python 3 . Vous pouvez modifier les arguments d'origine de l'exception et ajouter vos propres arguments.

Une exception se souvient des arguments avec lesquels elle a été créée. Je suppose que c'est pour que vous puissiez modifier l'exception.

Dans la fonction, reraisenous ajoutons les arguments d'origine de l'exception avec tous les nouveaux arguments que nous voulons (comme un message). Enfin, nous levons à nouveau l'exception tout en préservant l'historique de retraçage.

def reraise(e, *args):
  '''re-raise an exception with extra arguments
  :param e: The exception to reraise
  :param args: Extra args to add to the exception
  '''

  # e.args is a tuple of arguments that the exception with instantiated with.
  #
  e.args = args + e.args

  # Recreate the expection and preserve the traceback info so thta we can see 
  # where this exception originated.
  #
  raise e.with_traceback(e.__traceback__)   


def bad():
  raise ValueError('bad')

def very():
  try:
    bad()
  except Exception as e:
    reraise(e, 'very')

def very_very():
  try:
    very()
  except Exception as e:
    reraise(e, 'very')

very_very()

production

Traceback (most recent call last):
  File "main.py", line 35, in <module>
    very_very()
  File "main.py", line 30, in very_very
    reraise(e, 'very')
  File "main.py", line 15, in reraise
    raise e.with_traceback(e.__traceback__)
  File "main.py", line 28, in very_very
    very()
  File "main.py", line 24, in very
    reraise(e, 'very')
  File "main.py", line 15, in reraise
    raise e.with_traceback(e.__traceback__)
  File "main.py", line 22, in very
    bad()
  File "main.py", line 18, in bad
    raise ValueError('bad')
ValueError: ('very', 'very', 'bad')
musaraigne
la source
1
De loin la meilleure réponse. C'est la seule réponse qui répond à la question d'origine, préserve le traçage d'origine et est pure-Python 3.x. Accessoires fous pour frapper aussi ce tambour de meme "très, très mauvais". L'humour est incontestablement une bonne chose - surtout dans des réponses techniques autrement sèches comme celle-ci. Bravo!
Cecil Curry
0

Aucune des solutions ci-dessus n'a fait exactement ce que je voulais, c'est-à-dire ajouter des informations à la première partie du message d'erreur, c'est-à-dire que je voulais que mes utilisateurs voient d'abord mon message personnalisé.

Cela a fonctionné pour moi:

exception_raised = False
try:
    do_something_that_might_raise_an_exception()
except ValueError as e:
    message = str(e)
    exception_raised = True

if exception_raised:
    message_to_prepend = "Custom text"
    raise ValueError(message_to_prepend + message)
RobinL
la source
-4

si vous souhaitez personnaliser le type d'erreur, une chose simple que vous pouvez faire est de définir une classe d'erreur basée sur ValueError.

igni
la source
comment cela aiderait-il dans ce cas?
Johan Lundberg
... imaginez en fait penser que c'était une réponse valable.
Cecil Curry