web-dev-qa-db-fra.com

"Exception intérieure" (avec traceback) en Python?

Mon expérience est en C # et j'ai récemment commencé à programmer en Python. Lorsqu'une exception est levée, je veux généralement l'encapsuler dans une autre exception qui ajoute plus d'informations, tout en affichant la trace complète de la pile. C'est assez facile en C #, mais comment faire en Python?

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

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 la trace de l'exception intérieure!

Edit: J'aimerais voir les messages d'exception et les 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à-bas - comme je le ferais en C #. Est-ce possible en Python 2.6? On dirait que le mieux que je puisse faire jusqu'à présent (basé sur 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 traces, mais cela ne montre pas quelle exception s'est produite à l'endroit où dans la trace.

128
EMP

Python 2

C'est simple; passez le traceback comme troisième argument à soulever.

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 attrapez une exception et que vous en relancez une autre.

132
Glenn Maynard

Python 3

Dans 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")
203
Alexei Tenitski

Python 3 a la clause raise ... from pour enchaîner les exceptions. Réponse de Glenn est idéal pour Python 2.7, mais il n'utilise que le retraçage de l'exception d'origine et jette le message d'erreur et d'autres détails. Voici quelques exemples dans Python 2.7 qui ajoute des informations de contexte de la portée actuelle dans le message d'erreur de l'exception d'origine, mais garde 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 saveur de instruction raise 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 traceback comme troisième expression. Si vous exécutez avant 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 à intercepter. L'inconvénient est qu'il perd le type d'exception et déclenche simplement un RuntimeError. Vous devez importer le module traceback.

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 affiche la ligne où check_output() a été appelée, mais le message d'exception inclut désormais la ligne de commande.

18
Don Kirkby

Dans Python 3.x :

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

ou simplement

except Exception:
    raise MyException()

qui propagera MyException mais affichera les deux exceptions si elle n'est 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 le __context__ attribut. Ici, j'écris un gestionnaire de contexte qui utilise cela pour attraper et changer 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
10
ilya n.

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

Dans l'implémentation actuelle de Python, les exceptions sont composées de trois parties: le type, la valeur et le suivi. 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 'raise' 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 sujette aux erreurs. En outre, l'instruction "except" ne peut donner accès qu'à la valeur, pas au traceback. Ajout de l'attribut " traceback " à l'exception valeurs 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] indique que "Lorsqu'une exception X est levée comme résultat direct d'une exception Y précédente, la propriété InnerException de X doit contenir une référence à Y." Cette propriété n'est pas définie par la VM automatiquement; tous les constructeurs d'exceptions acceptent plutôt un argument 'innerException' facultatif pour la définir explicitement. La cause ' 'attribut remplit le même but que InnerException, mais ce PEP propose une nouvelle forme de' lever '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; ce PEP ne propose aucun analogue.

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

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

5
ire_and_curses

Pour une compatibilité maximale entre Python 2 et 3, vous pouvez utiliser raise_from dans la bibliothèque six. 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)
3
LexieHankins

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 dans si vous voulez donner plus d'une exception interceptée comme cause à une nouvelle exception levée. Peut-être que cela peut vous aider.

3
Alfe

En supposant:

  • vous avez besoin d'une solution qui fonctionne pour Python 2 (pour pure Python 3 voir raise ... from Solution)
  • je veux juste enrichir le message d'erreur, par ex. fournir un contexte supplémentaire
  • besoin de la trace complète de la pile

vous pouvez utiliser une solution simple à partir 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é "augmenter" simplifié qui est autonome. Cela relèvera l'exception dans le bloc except.

2
atreat

Vous pourriez peut-être 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
2
brool