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?
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')
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.
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.
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
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.
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!
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)