174 votes

"Inner exception" (avec traceback) en Python ?

J'ai une formation en C# et je viens de commencer à programmer en Python. Lorsqu'une exception est levée, je veux 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 facile en C#, mais comment puis-je le 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 la trace de l'exception interne !

Edit : J'aimerais voir les deux messages d'exception et les deux traces de pile et faire la corrélation entre les deux. En d'autres termes, je veux voir dans la sortie que l'exception X s'est produite ici et ensuite 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 (sur la base de la réponse de Glenn Maynard) soit :

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

Cela inclut les deux messages et les deux traces, mais cela ne montre pas quelle exception s'est produite à quel endroit dans la trace.

3 votes

La réponse acceptée commence à dater, peut-être devriez-vous envisager d'en accepter une autre.

1 votes

@AaronHall malheureusement OP n'a pas été vu depuis 2015.

329voto

Alexei Tenitski Points 2410

Python 3

En python 3, vous pouvez faire ce qui suit :

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

except MyExceptionToBeWrapped as e:

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

Cela donnera 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")

29 votes

raise ... from ... est en effet la manière correcte de procéder dans Python 3. Cela nécessite plus de votes positifs.

0 votes

Nakedible Je pense que c'est parce que, malheureusement, la plupart des gens n'utilisent pas encore Python 3.

0 votes

Cela semble se produire même en utilisant 'from' dans python 3.

143voto

Glenn Maynard Points 24451

Python 2

C'est simple, passez le traceback comme troisième argument à raise.

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 en relancez une autre.

4 votes

Merci. Cela préserve le retour de trace, mais perd le message d'erreur de l'exception originale. Comment puis-je voir les deux messages et les deux retours de trace ?

7 votes

raise MyException(str(e)), ... etc.

0 votes

Une seule trace est toujours transmise vers le haut. Si le premier attrapeur fait des choses compliquées comme appeler d'autres fonctions pour relancer l'exception attrapée, la trace de ces choses est perdue. Pour conserver plus d'une trace, vous pouvez jeter un coup d'oeil à ma réponse ci-dessous.

21voto

Don Kirkby Points 12671

Python 3 dispose de la fonction raise ... from clause pour enchaîner les exceptions. La réponse de Glenn est idéal pour Python 2.7, mais il n'utilise que le retour de trace de l'exception originale et supprime le message d'erreur et les autres détails. Voici quelques exemples dans Python 2.7 qui ajoutent des informations contextuelles de la portée actuelle dans le message d'erreur de l'exception originale, mais qui conservent 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 raise déclaration prend le type d'exception comme première expression, les arguments du constructeur de la classe d'exception dans un tuple comme deuxième expression, et le traceback comme troisième expression. Si vous utilisez une version antérieure à Python 2.2, lisez les avertissements sur sys.exc_info() .

Tout type d'exception

Voici un autre exemple à usage plus général si vous ne savez pas quel type d'exceptions votre code pourrait avoir à attraper. L'inconvénient est qu'il perd le type d'exception et soulève simplement une RuntimeError. Vous devez importer le fichier traceback module.

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 un contexte. Vous pouvez modifier le message de l'exception, puis la 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ù check_output() a été appelé, mais le message d'exception inclut maintenant la ligne de commande.

1 votes

Où se trouve le ex.strerror venant de ? Je ne trouve aucun résultat pertinent à ce sujet dans la documentation Python. Cela ne devrait-il pas être str(ex) ?

1 votes

IOError est dérivé de EnvironmentError , @hheimbuerger, qui fournit la errorno y strerror attributs.

0 votes

Comment puis-je emballer un Error par exemple ValueError, en un message de type RuntimeError en attrapant Exception ? Si je reproduis votre réponse pour ce cas, le stacktrace est perdu.

15voto

ilya n. Points 6610

Sur Python 3.x :

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

ou tout simplement

except Exception:
    raise MyException()

qui se propagera MyException mais imprimer les deux des exceptions si elle ne sera pas traitée.

Sur Python 2.x :

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

Vous pouvez empêcher l'impression de ces deux exceptions en tuant le __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

5 votes

TypeError : raise : arg 3 doit être un traceback ou None

0 votes

Désolé, j'ai fait une erreur, je pensais qu'il acceptait aussi les exceptions et obtenait automatiquement leur attribut de traceback. D'après docs.python.org/3.1/reference/ ce qui devrait être e.__traceback__.

1 votes

@ilyan.. : Python 2 n'a pas e.__traceback__ attribut !

5voto

ire_and_curses Points 32802

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. À partir de PEP 3134 :

Dans l'implémentation actuelle de Python, les exceptions sont composées de trois parties : le type, la valeur et le retour de trace. Le module 'sys', expose l'exception courante dans trois variables parallèles, exc_type, exc_value, et exc_traceback, la fonction sys.exc_info() retourne un un tuple de ces trois parties, et l'instruction 'raise' a un forme à trois arguments acceptant ces trois parties. La manipulation de exceptions nécessite souvent de passer ces trois choses en parallèle, ce qui peut être fastidieux et source d'erreurs. De plus, l'instruction "except ne peut donner accès qu'à la valeur, et non à la traceback. L'ajout de l'instruction ' Traceback L'attribut '' aux valeurs d'exception rend toutes les informations relatives aux exceptions sont accessibles à partir d'un seul endroit.

Comparaison avec C# :

Les exceptions en C# contiennent une propriété 'InnerException' en lecture seule qui qui peut pointer vers une autre exception. Sa documentation [10] indique que "Lorsqu'une exception X est déclenchée en tant que résultat direct d'une précédente exception précédente Y, la propriété InnerException de X doit contenir une référence à référence à Y". Cette propriété n'est pas définie automatiquement par la VM ; Au contraire, tous les constructeurs d'exception prennent un argument optionnel 'innerException' pour la définir exprès. pour la définir explicitement. L'argument ' cause L'attribut "InnerException" remplit même objectif que InnerException, mais ce PEP propose une nouvelle forme de de 'raise' plutôt que d'étendre les constructeurs de toutes les exceptions. C# fournit également une méthode GetBaseException qui permet de sauter directement à la 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 genre de choses non plus. Je cite à nouveau :

En ce qui concerne les autres langages, Java et Ruby abandonnent tous les deux l'élément original originale lorsqu'une autre exception se produit dans une clause 'catch'/'rescue' ou ou une clause "finally"/"ensure". Perl 5 n'intègre pas de gestion structurée des structurée intégrée. Pour Perl 6, la RFC numéro 88 [9] propose un mécanisme d'exception qui mécanisme d'exception qui conserve implicitement les exceptions enchaînées dans un tableau nommé @@.

0 votes

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X