web-dev-qa-db-fra.com

La trace d'exception est masquée si elle n'est pas sur-levée immédiatement

J'ai un morceau de code similaire à ceci:

import sys

def func1():
    func2()

def func2():
    raise Exception('test error')

def main():
    err = None

    try:
        func1()
    except:
        err = sys.exc_info()[1]
        pass

    # some extra processing, involving checking err details (if err is not None)

    # need to re-raise err so caller can do its own handling
    if err:
        raise err

if __== '__main__':
    main()

Quand func2 lève une exception Je reçois le retraçage suivant:

Traceback (most recent call last):
  File "err_test.py", line 25, in <module>
    main()
  File "err_test.py", line 22, in main
    raise err
Exception: test error

De là, je ne vois pas d'où vient l'exception. Le retraçage d'origine est perdu.

Comment puis-je conserver le retraçage d'origine et le ré-augmenter? Je veux voir quelque chose de similaire à ceci:

Traceback (most recent call last):
  File "err_test.py", line 26, in <module>
    main()
  File "err_test.py", line 13, in main
    func1()
  File "err_test.py", line 4, in func1
    func2()
  File "err_test.py", line 7, in func2
    raise Exception('test error')
Exception: test error
64
parxier

Un raise vide déclenche la dernière exception.

# need to re-raise err so caller can do its own handling
if err:
    raise

Si tu utilises raise something Python n'a aucun moyen de savoir si something était une exception qui vient d'être interceptée auparavant, ou une nouvelle exception avec une nouvelle trace de pile. C'est pourquoi il y a raise qui préserve la trace de la pile.

Référence ici

108
Jochen Ritzel

Il est possible de modifier et relancer une exception:

Si aucune expression n'est présente, raise relance la dernière exception qui était active dans la portée actuelle. Si aucune exception n'est active dans la portée actuelle, une exception TypeError est levée indiquant qu'il s'agit d'une erreur (si elle s'exécute sous IDLE, une Queue.Empty l'exception est levée à la place).

Sinon, raise évalue les expressions pour obtenir trois objets, en utilisant None comme valeur des expressions omises. Les deux premiers objets sont utilisés pour déterminer le type et la valeur de l'exception.

Si un troisième objet est présent et non None, il doit s'agir d'un objet traceback (voir la section La hiérarchie de types standard) et il est substitué à la place de l'emplacement actuel comme lieu où l'exception s'est produite. Si le troisième objet est présent et non un objet traceback ou None, une exception TypeError est levée.

La forme à trois expressions de raise est utile pour sur-lever une exception de manière transparente dans une clause except, mais raise sans expression doit être préféré si l'exception doit être re -raisé était la dernière exception active dans le champ d'application actuel.

Donc, si vous souhaitez modifier l'exception et la renvoyer, vous pouvez le faire:

try:
    buggy_code_which_throws_exception()
except Exception as e:
    raise Exception, "The code is buggy: %s" % e, sys.exc_info()[2]
62
qris

Vous pouvez obtenir beaucoup d'informations sur l'exception via la sys.exc_info() avec le module traceback

essayez l'extension suivante de votre code.

import sys
import traceback

def func1():
    func2()

def func2():
    raise Exception('test error')

def main():

    try:
        func1()
    except:
        exc_type, exc_value, exc_traceback = sys.exc_info()
        # Do your verification using exc_value and exc_traceback

        print "*** print_exception:"
        traceback.print_exception(exc_type, exc_value, exc_traceback,
                                  limit=3, file=sys.stdout)

if __== '__main__':
    main()

Cela s'imprimerait, semblable à ce que vous vouliez.

*** print_exception:
Traceback (most recent call last):
  File "err_test.py", line 14, in main
    func1()
  File "err_test.py", line 5, in func1
    func2()
  File "err_test.py", line 8, in func2
    raise Exception('test error')
Exception: test error
5
Senthil Kumaran

Bien que la réponse de @ Jochen fonctionne bien dans le cas simple, elle n'est pas capable de gérer des cas plus complexes, où vous n'attrapez et ne relancez pas directement, mais vous êtes pour une raison donnée l'exception en tant qu'objet et souhaitez réinjecter complètement nouveau contexte (c'est-à-dire si vous devez le gérer dans un processus différent).

Dans ce cas, je propose ce qui suit:

  1. obtenir l'original exc_info
  2. formater le message d'erreur d'origine, avec trace de pile
  3. lever une nouvelle exception avec ce message d'erreur complet (trace de pile incl.) incorporé

Avant de faire cela, définissez un nouveau type d'exception que vous renverrez plus tard ...

class ChildTaskException(Exception):
    pass

Dans le code incriminé ...

import sys
import traceback

try:
    # do something dangerous
except:
    error_type, error, tb = sys.exc_info()
    error_lines = traceback.format_exception(error_type, error, tb)
    error_msg = ''.join(error_lines)
    # for example, if you are doing multiprocessing, you might want to send this to another process via a pipe
    connection.send(error_msg)

Renouveler ...

# again, a multiprocessing example of receiving that message through a pipe
error_msg = pcon.recv()
raise ChildTaskException(error_msg)
4
tvt173

Votre fonction principale doit ressembler à ceci:

def main():
    try:
        func1()
    except Exception, err:
        # error processing
        raise

Il s'agit de la méthode standard de traitement (et de ré-augmentation) des erreurs. Voici une démonstration du codepad.

2
Gabi Purcaru