«Exception interne» (avec traceback) en Python?

146

Mon expérience est en C # et je viens de commencer à programmer en Python. Lorsqu'une exception est levée, je souhaite généralement l'envelopper dans une autre exception qui ajoute plus d'informations, tout en affichant la trace complète de la pile. C'est assez simple en C #, mais comment faire en Python?

Par exemple. en C # je ferais quelque chose comme ceci:

try
{
  ProcessFile(filePath);
}
catch (Exception ex)
{
  throw new ApplicationException("Failed to process file " + filePath, ex);
}

En Python, je peux faire quelque chose de similaire:

try:
  ProcessFile(filePath)
except Exception as e:
  raise Exception('Failed to process file ' + filePath, e)

... mais cela perd le traçage de l'exception interne!

Edit: Je voudrais voir les deux messages d'exception et les deux traces de pile et corréler les deux. Autrement dit, je veux voir dans la sortie que l'exception X s'est produite ici, puis l'exception Y là - comme je le ferais en C #. Est-ce possible dans Python 2.6? Il semble que le mieux que je puisse faire jusqu'à présent (d'après la réponse de Glenn Maynard) est:

try:
  ProcessFile(filePath)
except Exception as e:
  raise Exception('Failed to process file' + filePath, e), None, sys.exc_info()[2]

Cela inclut à la fois les messages et les deux retraits, mais cela ne montre pas quelle exception s'est produite où dans le retraçage.

EMP
la source
3
La réponse acceptée est dépassée, peut-être devriez-vous envisager d'en accepter une autre.
Aaron Hall
1
@AaronHall, malheureusement, OP n'a pas été vu depuis 2015.
Antti Haapala

Réponses:

136

Python 2

C'est simple; transmettez le retraçage comme troisième argument à lever.

import sys
class MyException(Exception): pass

try:
    raise TypeError("test")
except TypeError, e:
    raise MyException(), None, sys.exc_info()[2]

Faites toujours cela lorsque vous interceptez une exception et en relancez une autre.

Glenn Maynard
la source
4
Merci. Cela préserve la trace, mais perd le message d'erreur de l'exception d'origine. Comment voir les deux messages et les deux retraits?
EMP
6
raise MyException(str(e)), ..., etc.
Glenn Maynard
23
Python 3 ajoute raise E() from tbet.with_traceback(...)
Dima Tisnek
3
@GlennMaynard c'est une question assez ancienne, mais l'argument du milieu de raiseest la valeur à passer à l'exception (au cas où le premier argument serait une classe d'exception et non une instance). Donc , si vous voulez des exceptions d'échange, au lieu de le faire raise MyException(str(e)), None, sys.exc_info()[2], il est préférable d'utiliser: raise MyException, e.args, sys.exc_info()[2].
bgusach
8
Une méthode compatible Python2 et 3 est possible en utilisant le futur package: python-future.org/compatible_idioms.html#raising-exceptions Par exemple from future.utils import raise_et raise_(ValueError, None, sys.exc_info()[2]).
jtpereyda
239

Python 3

En python 3, vous pouvez effectuer les opérations suivantes:

try:
    raise MyExceptionToBeWrapped("I have twisted my ankle")

except MyExceptionToBeWrapped as e:

    raise MyWrapperException("I'm not in a good shape") from e

Cela produira quelque chose comme ceci:

   Traceback (most recent call last):
   ...
   MyExceptionToBeWrapped: ("I have twisted my ankle")

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

   Traceback (most recent call last):
   ...
   MyWrapperException: ("I'm not in a good shape")
Alexei Tenitski
la source
17
raise ... from ...est en effet la bonne façon de faire cela en Python 3. Cela nécessite plus de votes positifs.
Nakedible le
NakedibleJe pense que c'est parce que, malheureusement, la plupart des gens n'utilisent toujours pas Python 3.
Tim Ludwinski
Cela semble se produire même avec l'utilisation de 'from' dans Python 3
Steve Vermeulen
Pourrait être rétroporté vers Python 2. J'espère qu'il le sera un jour.
Marcin Wojnarski
4
@ogrisel Vous pouvez utiliser le futurepackage pour y parvenir: python-future.org/compatible_idioms.html#raising-exceptions Par exemple from future.utils import raise_et raise_(ValueError, None, sys.exc_info()[2]).
jtpereyda
19

Python 3 a la clause raise...from pour enchaîner les exceptions. La réponse de Glenn est excellente pour Python 2.7, mais elle n'utilise que le traçage de l'exception d'origine et supprime le message d'erreur et d'autres détails. Voici quelques exemples en Python 2.7 qui ajoutent des informations de contexte de la portée actuelle dans le message d'erreur de l'exception d'origine, mais gardent les autres détails intacts.

Type d'exception connu

try:
    sock_common = xmlrpclib.ServerProxy(rpc_url+'/common')
    self.user_id = sock_common.login(self.dbname, username, self.pwd)
except IOError:
    _, ex, traceback = sys.exc_info()
    message = "Connecting to '%s': %s." % (config['connection'],
                                           ex.strerror)
    raise IOError, (ex.errno, message), traceback

Cette variante d' raiseinstruction prend le type d'exception comme première expression, les arguments du constructeur de classe d'exception dans un tuple comme deuxième expression et le retraçage comme troisième expression. Si vous exécutez une version antérieure à Python 2.2, consultez les avertissements sur sys.exc_info().

Tout type d'exception

Voici un autre exemple plus général si vous ne savez pas quel type d'exceptions votre code pourrait avoir à détecter. L'inconvénient est qu'il perd le type d'exception et déclenche simplement une erreur RuntimeError. Vous devez importer le tracebackmodule.

except Exception:
    extype, ex, tb = sys.exc_info()
    formatted = traceback.format_exception_only(extype, ex)[-1]
    message = "Importing row %d, %s" % (rownum, formatted)
    raise RuntimeError, message, tb

Modifier le message

Voici une autre option si le type d'exception vous permet d'y ajouter du contexte. Vous pouvez modifier le message de l'exception, puis le relancer.

import subprocess

try:
    final_args = ['lsx', '/home']
    s = subprocess.check_output(final_args)
except OSError as ex:
    ex.strerror += ' for command {}'.format(final_args)
    raise

Cela génère la trace de pile suivante:

Traceback (most recent call last):
  File "/mnt/data/don/workspace/scratch/scratch.py", line 5, in <module>
    s = subprocess.check_output(final_args)
  File "/usr/lib/python2.7/subprocess.py", line 566, in check_output
    process = Popen(stdout=PIPE, *popenargs, **kwargs)
  File "/usr/lib/python2.7/subprocess.py", line 710, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1327, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory for command ['lsx', '/home']

Vous pouvez voir qu'il montre la ligne où a check_output()été appelé, mais le message d'exception inclut maintenant la ligne de commande.

Don Kirkby
la source
1
D'où ex.strerrorvient-il? Je ne trouve aucun hit pertinent pour cela dans la documentation Python. Cela ne devrait-il pas être le cas str(ex)?
Henrik Heimbuerger
1
IOErrorest dérivé de EnvironmentError@hheimbuerger, qui fournit les attributs errornoet strerror.
Don Kirkby
Comment envelopper un arbitraire Error, par exemple ValueError, dans un RuntimeErrorby catch Exception? Si je reproduis votre réponse pour ce cas, le stacktrace est perdu.
Karl Richter
Je ne sais pas ce que vous demandez, @karl. Pouvez-vous publier un échantillon dans une nouvelle question, puis créer un lien vers celle-ci à partir d'ici?
Don Kirkby le
J'ai édité mon duplicata de la question de l'OP à stackoverflow.com/questions/23157766/… avec une clarification prenant en compte directement votre réponse. On devrait en discuter :)
Karl Richter
12

Dans Python 3.x :

raise Exception('Failed to process file ' + filePath).with_traceback(e.__traceback__)

ou simplement

except Exception:
    raise MyException()

qui se propagera MyExceptionmais affichera les deux exceptions si elle ne sera pas gérée.

Dans Python 2.x :

raise Exception, 'Failed to process file ' + filePath, e

Vous pouvez empêcher l'impression des deux exceptions en supprimant l' __context__attribut. Ici, j'écris un gestionnaire de contexte qui l'utilise pour attraper et modifier votre exception à la volée: (voir http://docs.python.org/3.1/library/stdtypes.html pour une explication de leur fonctionnement)

try: # Wrap the whole program into the block that will kill __context__.

    class Catcher(Exception):
        '''This context manager reraises an exception under a different name.'''

        def __init__(self, name):
            super().__init__('Failed to process code in {!r}'.format(name))

        def __enter__(self):
            return self

        def __exit__(self, exc_type, exc_val, exc_tb):
            if exc_type is not None:
                self.__traceback__ = exc_tb
                raise self

    ...


    with Catcher('class definition'):
        class a:
            def spam(self):
                # not really pass, but you get the idea
                pass

            lut = [1,
                   3,
                   17,
                   [12,34],
                   5,
                   _spam]


        assert a().lut[-1] == a.spam

    ...


except Catcher as e:
    e.__context__ = None
    raise
ilya n.
la source
4
TypeError: rise: arg 3 doit être un traceback ou None
Glenn Maynard
Désolé, j'ai fait une erreur, j'ai pensé qu'il accepte également les exceptions et obtient automatiquement leur attribut de traceback. Selon docs.python.org/3.1/reference/… , cela devrait être e .__
traceback__
1
@ilyan .: Python 2 n'a pas d' e.__traceback__attribut!
Jan Hudec
5

Je ne pense pas que vous puissiez faire cela dans Python 2.x, mais quelque chose de similaire à cette fonctionnalité fait partie de Python 3. De PEP 3134 :

Dans l'implémentation Python actuelle, les exceptions sont composées de trois parties: le type, la valeur et le traçage. Le module 'sys' expose l'exception actuelle dans trois variables parallèles, exc_type, exc_value et exc_traceback, la fonction sys.exc_info () renvoie un tuple de ces trois parties et l'instruction 'rise' a une forme à trois arguments acceptant ces trois parties. La manipulation des exceptions nécessite souvent de passer ces trois choses en parallèle, ce qui peut être fastidieux et sujet aux erreurs. En outre, l'instruction 'except' ne peut fournir l'accès qu'à la valeur, pas à la trace. L'ajout de l' attribut « traceback » aux valeurs d'exception rend toutes les informations d'exception accessibles à partir d'un seul endroit.

Comparaison avec C #:

Les exceptions en C # contiennent une propriété 'InnerException' en lecture seule qui peut pointer vers une autre exception. Sa documentation [10] dit que "Lorsqu'une exception X est levée comme résultat direct d'une précédente exception Y, la propriété InnerException de X devrait contenir une référence à Y." Cette propriété n'est pas définie automatiquement par la machine virtuelle; au contraire, tous les constructeurs d'exceptions prennent un argument optionnel 'innerException' pour le définir explicitement. L' attribut ' cause ' remplit le même but que InnerException, mais ce PEP propose une nouvelle forme de 'augmenter' plutôt que d'étendre les constructeurs de toutes les exceptions. C # fournit également une méthode GetBaseException qui saute directement à la fin de la chaîne InnerException;

Notez également que Java, Ruby et Perl 5 ne supportent pas non plus ce type de chose. Citant à nouveau:

Comme pour les autres langages, Java et Ruby rejettent tous deux l'exception d'origine lorsqu'une autre exception se produit dans une clause «catch» / «rescue» ou «finally» / «ensure». Perl 5 ne dispose pas de gestion des exceptions structurée intégrée. Pour Perl 6, le numéro RFC 88 [9] propose un mécanisme d'exception qui retient implicitement les exceptions chaînées dans un tableau nommé @@.

ire_and_curses
la source
Mais, bien sûr, en Perl5, vous pouvez simplement dire "confesser qq {OH NON! $ @}" Et ne pas perdre la trace de pile de l'autre exception. Ou vous pouvez implémenter votre propre type qui conserve l'exception.
jrockway
4

Pour une compatibilité maximale entre Python 2 et 3, vous pouvez utiliser raise_fromdans la sixbibliothèque. https://six.readthedocs.io/#six.raise_from . Voici votre exemple (légèrement modifié pour plus de clarté):

import six

try:
  ProcessFile(filePath)
except Exception as e:
  six.raise_from(IOError('Failed to process file ' + repr(filePath)), e)
LexH
la source
3

Vous pouvez utiliser ma classe CausedException pour enchaîner les exceptions dans Python 2.x (et même dans Python 3, cela peut être utile si vous souhaitez donner plus d'une exception interceptée comme cause d'une exception nouvellement levée). Peut-être que cela peut vous aider.

Alfe
la source
2

Peut-être pourriez-vous saisir les informations pertinentes et les transmettre? Je pense à quelque chose comme:

import traceback
import sys
import StringIO

class ApplicationError:
    def __init__(self, value, e):
        s = StringIO.StringIO()
        traceback.print_exc(file=s)
        self.value = (value, s.getvalue())

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

try:
    try:
        a = 1/0
    except Exception, e:
        raise ApplicationError("Failed to process file", e)
except Exception, e:
    print e
brool
la source
2

En supposant:

  • vous avez besoin d'une solution, qui fonctionne pour Python 2 (pour Python 3 pur, voir raise ... fromsolution)
  • veulent juste enrichir le message d'erreur, par exemple en fournissant un contexte supplémentaire
  • besoin de la trace complète de la pile

vous pouvez utiliser une solution simple de la documentation https://docs.python.org/3/tutorial/errors.html#raising-exceptions :

try:
    raise NameError('HiThere')
except NameError:
    print 'An exception flew by!' # print or log, provide details about context
    raise # reraise the original exception, keeping full stack trace

Le résultat:

An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: HiThere

Il semble que l'élément clé soit le mot-clé simplifié «augmenter» qui est autonome. Cela relèvera l'exception dans le bloc except.

un régal
la source
C'est la solution compatible Python 2 & 3! Merci!
Andy Chase
Je pense que l'idée était de soulever un autre type d'exception.
Tim Ludwinski
2
Ce n'est pas une chaîne d'exceptions imbriquées, il suffit de relancer une exception
Karl Richter
C'est la meilleure solution python 2, si vous avez juste besoin d'enrichir le message d'exception et d'avoir la trace complète de la pile!
geekQ
Quelle est la différence entre augmenter et augmenter de
variable du