web-dev-qa-db-fra.com

Ajouter des informations à une exception?

Je veux réaliser quelque chose comme ça:

def foo():
   try:
       raise IOError('Stuff ')
   except:
       raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
       e.message = e.message + 'happens at %s' % arg1
       raise

bar('arg1')
Traceback...
  IOError('Stuff Happens at arg1')

Mais ce que je reçois c'est:

Traceback..
  IOError('Stuff')

Des indices sur la manière d’y parvenir? Comment faire les deux dans Python 2 et 3?

105
anijhaw

Je le ferais comme ça, donc changer son type dans foo() ne nécessitera pas de le changer aussi dans bar().

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
        foo()
    except Exception as e:
        raise type(e)(e.message + ' happens at %s' % arg1)

bar('arg1')
Traceback (most recent call last):
  File "test.py", line 13, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    raise type(e)(e.message + ' happens at %s' % arg1)
IOError: Stuff happens at arg1

Mise à jour 1

Voici une légère modification qui conserve le traçage d'origine:

...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e), type(e)(e.message +
                               ' happens at %s' % arg1), sys.exc_info()[2]

bar('arg1')
Traceback (most recent call last):
  File "test.py", line 16, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    foo()
  File "test.py", line 5, in foo
    raise IOError('Stuff')
IOError: Stuff happens at arg1

Mise à jour 2

Pour Python 3.x, le code de ma première mise à jour est syntaxiquement incorrect, plus l’idée d’avoir un attribut message sur BaseException était retirée dans un changement de PEP 352 le 2012-05-16 (ma première mise à jour a été publiée le 2012-03-12). Donc actuellement, dans Python 3.5.2 de toute façon, vous ' Il est nécessaire de faire quelque chose dans ce sens pour préserver la trace et non le type d'exception dans la fonction bar(). Notez également qu'il y aura la ligne suivante:

During handling of the above exception, another exception occurred:

dans les messages de suivi affichés.

# for Python 3.x
...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e)(str(e) +
                      ' happens at %s' % arg1).with_traceback(sys.exc_info()[2])

bar('arg1')

Mise à jour 3

Un intervenant a demandé s’il existait un moyen qui fonctionnerait dans les deux Python 2 et 3. Bien que la réponse puisse sembler être "Non" en raison des différences de syntaxe, il y a est un moyen de contourner ce problème en utilisant une fonction d'assistance comme reraise() dans le module complémentaire six . Donc, si vous préférez ne pas utiliser la bibliothèque pour une raison quelconque, voici une version autonome simplifiée.

Notez également que, puisque l'exception est générée de nouveau dans la fonction reraise(), elle apparaîtra dans la trace générée, mais le résultat final correspond à vos souhaits.

import sys

if sys.version_info.major < 3:  # Python 2?
    # Using exec avoids a SyntaxError in Python 3.
    exec("""def reraise(exc_type, exc_value, exc_traceback=None):
                raise exc_type, exc_value, exc_traceback""")
else:
    def reraise(exc_type, exc_value, exc_traceback=None):
        if exc_value is None:
            exc_value = exc_type()
        if exc_value.__traceback__ is not exc_traceback:
            raise exc_value.with_traceback(exc_traceback)
        raise exc_value

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
        reraise(type(e), type(e)(str(e) +
                                 ' happens at %s' % arg1), sys.exc_info()[2])

bar('arg1')
95
martineau

Au cas où vous seriez venu chercher une solution pour Python 3 le manuel dit:

Lors de la levée d'une nouvelle exception (plutôt que d'utiliser un raise nu pour remonter l'exception en cours de traitement), le contexte de l'exception implicite peut être complété par une cause explicite en utilisant from avec raise:

raise new_exc from original_exc

Exemple:

try:
    return [permission() for permission in self.permission_classes]
except TypeError as e:
    raise TypeError("Make sure your view's 'permission_classes' are iterable. "
                    "If you use '()' to generate a set with a single element "
                    "make sure that there is a comma behind the one (element,).") from e

Ce qui ressemble à ceci à la fin:

2017-09-06 16:50:14,797 [ERROR] Django.request: Internal Server Error: /v1/sendEmail/
Traceback (most recent call last):
File "venv/lib/python3.4/site-packages/rest_framework/views.py", line 275, in get_permissions
    return [permission() for permission in self.permission_classes]
TypeError: 'type' object is not iterable 

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

Traceback (most recent call last):
    # Traceback removed...
TypeError: Make sure your view's Permission_classes are iterable. If 
     you use parens () to generate a set with a single element make 
     sure that there is a (comma,) behind the one element.

Transformer un TypeError totalement absurde en un joli message avec des indices pour une solution sans gâcher l'Exception originale.

61
Chris

En supposant que vous ne vouliez pas ou que vous ne puissiez pas modifier foo (), vous pouvez faire ceci:

try:
    raise IOError('stuff')
except Exception as e:
    if len(e.args) >= 1:
        e.args = (e.args[0] + ' happens',) + e.args[1:]
    raise

C’est en effet la seule solution ici qui résout le problème dans le message Python 3 sans un message laide et déroutant "Lors du traitement de l’exception ci-dessus, une autre exception s’est produite".

Si la ligne de relance doit être ajoutée à la trace de la pile, écrivez raise e au lieu de raise fera l'affaire.

17
Steve Howard

Une approche pratique que j'ai utilisée consiste à utiliser l'attribut de classe comme stockage pour plus de détails, cet attribut étant accessible à la fois à partir d'objet de classe et d'instance de classe:

class CustomError(Exception):
    details = None

Puis dans votre code:

exc = CustomError('Some message')
exc.details('Details -- add whatever you want')
raise exc

Et quand vous attrapez une erreur:

except CustomError, e:
    # Do whatever you want with the exception instance
    print e
    print e.details
3
Kee

Je fournirai un extrait de code que j'utilise souvent lorsque je veux ajouter des informations supplémentaires à une exception. Je travaille à la fois dans Python 2.7 et 3.6.

import sys
import traceback

try:
    a = 1
    b = 1j

    # The line below raises an exception because
    # we cannot compare int to complex.
    m = max(a, b)  

except Exception as ex:
    # I create my  informational message for debugging:
    msg = "a=%r, b=%r" % (a, b)

    # Gather the information from the original exception:
    exc_type, exc_value, exc_traceback = sys.exc_info()

    # Format the original exception for a Nice printout:
    traceback_string = ''.join(traceback.format_exception(
        exc_type, exc_value, exc_traceback))

    # Re-raise a new exception of the same class as the original one, 
    # using my custom message and the original traceback:
    raise type(ex)("%s\n\nORIGINAL TRACEBACK:\n\n%s\n" % (msg, traceback_string))

Le code ci-dessus génère le résultat suivant:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-09b74752c60d> in <module>()
     14     raise type(ex)(
     15         "%s\n\nORIGINAL TRACEBACK:\n\n%s\n" %
---> 16         (msg, traceback_string))

TypeError: a=1, b=1j

ORIGINAL TRACEBACK:

Traceback (most recent call last):
  File "<ipython-input-6-09b74752c60d>", line 7, in <module>
    m = max(a, b)  # Cannot compare int to complex
TypeError: no ordering relation is defined for complex numbers


Je sais que cela s'écarte un peu de l'exemple fourni dans la question, mais j'espère néanmoins que quelqu'un le trouvera utile.

2
Pedro M Duarte

Contrairement aux réponses précédentes, cela fonctionne malgré des exceptions avec de très mauvais __str__. Il ne modifie cependant le type, afin de factoriser les informations inutiles __str__ implémentations.

J'aimerais toujours trouver une amélioration supplémentaire qui ne modifie pas le type.

from contextlib import contextmanager
@contextmanager
def helpful_info():
    try:
        yield
    except Exception as e:
        class CloneException(Exception): pass
        CloneException.__= type(e).__name__
        CloneException.__module___ = type(e).__module__
        helpful_message = '%s\n\nhelpful info!' % e
        import sys
        raise CloneException, helpful_message, sys.exc_traceback


class BadException(Exception):
    def __str__(self):
        return 'wat.'

with helpful_info():
    raise BadException('fooooo')

Le suivi d'origine et le type (nom) sont conservés.

Traceback (most recent call last):
  File "re_raise.py", line 20, in <module>
    raise BadException('fooooo')
  File "/usr/lib64/python2.6/contextlib.py", line 34, in __exit__
    self.gen.throw(type, value, traceback)
  File "re_raise.py", line 5, in helpful_info
    yield
  File "re_raise.py", line 20, in <module>
    raise BadException('fooooo')
__main__.BadException: wat.

helpful info!
2
bukzor

Vous pouvez définir votre propre exception qui hérite d'une autre et créer son propre constructeur pour définir la valeur.

Par exemple:

class MyError(Exception):
   def __init__(self, value):
     self.value = value
     Exception.__init__(self)

   def __str__(self):
     return repr(self.value)
1