Re-raising exceptions with modified message

  • Thread starter Christoph Zwerschke
  • Start date
C

Christoph Zwerschke

What is the best way to re-raise any exception with a message
supplemented with additional information (e.g. line number in a
template)? Let's say for simplicity I just want to add "sorry" to every
exception message. My naive solution was this:

try:
...
except Exception, e:
raise e.__class__, str(e) + ", sorry!"

This works pretty well for most exceptions, e.g.
.... 1/0
.... except Exception, e:
.... raise e.__class__, str(e) + ", sorry!"
....
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
ZeroDivisionError: integer division or modulo by zero, sorry!

But it fails for some exceptions that cannot be instantiated with a
single string argument, like UnicodeDecodeError which gets "converted"
to a TypeError:
.... unicode('\xe4')
.... except Exception, e:
.... raise e.__class__, str(e) + ", sorry!"
....
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
TypeError: function takes exactly 5 arguments (1 given)

Another approach is using a wrapper Extension class:

class SorryEx(Exception):
def __init__(self, e):
self._e = e
def __getattr__(self, name):
return getattr(self._e, name)
def __str__(self):
return str(self._e) + ", sorry!"

try:
unicode('\xe4')
except Exception, e:
raise SorryEx(e)

But then I get the name of the wrapper class in the message:

__main__.SorryEx: 'ascii' codec can't decode byte 0xe4 in position 0:
ordinal not in range(128), sorry!

Yet another approach would be to replace the __str__ method of e, but
this does not work for new style Exceptions (Python 2.5).

Any suggestions?

-- Chris
 
T

Thomas Heller

Christoph said:
What is the best way to re-raise any exception with a message
supplemented with additional information (e.g. line number in a
template)?

I have the impression that you do NOT want to change the exceptions,
instead you want to print the traceback in a customized way. But I may be wrong...

Thomas
 
C

Christoph Zwerschke

Thomas said:
I have the impression that you do NOT want to change the exceptions,
instead you want to print the traceback in a customized way. But I may be wrong...

No, I really want to modify the exception, supplementing its message
with additional information about the state of the program.

The use case are compiled Kid templates (http://kid-templating.org). If
an error occurs, I want to add the corresponding line number of the XML
file as information for the template developer. Since the final error
handling may happen somewhere else (e.g. by TurboGears importing a Kid
template), I do not want to modify trackeback handling or something.

-- Chris
 
N

Neil Cerutti

No, I really want to modify the exception, supplementing its
message with additional information about the state of the
program.

The use case are compiled Kid templates
(http://kid-templating.org). If an error occurs, I want to add
the corresponding line number of the XML file as information
for the template developer. Since the final error handling may
happen somewhere else (e.g. by TurboGears importing a Kid
template), I do not want to modify trackeback handling or
something.

The documentation for BaseException contains something that might
be relevant:

[...] If more data needs to be attached to the exception,
attach it through arbitrary attributes on the instance. All
arguments are also stored in args as a tuple, but it will
eventually be deprecated and thus its use is discouraged. New
in version 2.5. [...]

I don't know if something like the following would help:
.... try:
.... 12/0
.... except ZeroDivisionError, e:
.... e.my_info = "Oops!"
.... raise
........ foo()
.... except ZeroDivisionError, e:
.... print e.my_info
....
Oops!

Users could get at the extra info you attached, but it wouldn't
be automatically displayed by the interpreter.
 
C

Christoph Zwerschke

Neil said:
The documentation for BaseException contains something that might
be relevant:

[...] If more data needs to be attached to the exception,
attach it through arbitrary attributes on the instance. All

Users could get at the extra info you attached, but it wouldn't
be automatically displayed by the interpreter.

Yes, that's the problem here. It wouldn't be displayed automatically and
the users must be aware of this attribute. I'd like to have a more
transparent solution.

-- Chris
 
N

Neil Cerutti

Neil said:
The documentation for BaseException contains something that might
be relevant:

[...] If more data needs to be attached to the exception,
attach it through arbitrary attributes on the instance. All

Users could get at the extra info you attached, but it wouldn't
be automatically displayed by the interpreter.

Yes, that's the problem here. It wouldn't be displayed
automatically and the users must be aware of this attribute.
I'd like to have a more transparent solution.

You ought to be able to use the third arg of raise to raise a new
exception as if it were from the previous location, but now with
a new message.

You may need the traceback module to get at the error message, if
trying to read e.message can fail.

Something like this mess here: ;)

...
except Exception, e:
etype, evalue, etb = sys.exc_info()
ex = traceback.format_exception_only(etype, evalue)
message = ex[0].partition(':')[2].strip()
raise etype, message+". Sorry!", etb

Note that the above will break for SyntaxError (who's message
contains more than one line) and any kind of exception that
doesn't inherit from Exception.

You might need some crufty try, finally to avoid having a
circular reference hang around, according to the docs for
sys.exc_info.
 
C

Christoph Zwerschke

Neil said:
You may need the traceback module to get at the error message, if
trying to read e.message can fail.

Something like this mess here: ;)

...
except Exception, e:
etype, evalue, etb = sys.exc_info()
ex = traceback.format_exception_only(etype, evalue)
message = ex[0].partition(':')[2].strip()
raise etype, message+". Sorry!", etb

Note that the above will break for SyntaxError (who's message
contains more than one line) and any kind of exception that
doesn't inherit from Exception.

That's actually similar to what I was using in Kid already.

The problem is that there are some Exceptions which cannot be
instantiated with a single string argument, such as UnicodeDeocdeError.
Please try the above with "unicode('\xe4')" instead of the dots.
Instead of re-raising the UnicodeDecodeError, you will get a TypeError
because of this problem.

-- Chris
 
N

Neil Cerutti

Neil said:
You may need the traceback module to get at the error message, if
trying to read e.message can fail.

Something like this mess here: ;)

...
except Exception, e:
etype, evalue, etb = sys.exc_info()
ex = traceback.format_exception_only(etype, evalue)
message = ex[0].partition(':')[2].strip()
raise etype, message+". Sorry!", etb

Note that the above will break for SyntaxError (who's message
contains more than one line) and any kind of exception that
doesn't inherit from Exception.

That's actually similar to what I was using in Kid already.

The problem is that there are some Exceptions which cannot be
instantiated with a single string argument, such as
UnicodeDeocdeError. Please try the above with "unicode('\xe4')"
instead of the dots. Instead of re-raising the
UnicodeDecodeError, you will get a TypeError because of this
problem.

Crud. After my third answer, I'll finally understand the
question. Unfortunately, I only had two in me.
 
K

Kay Schluehr

What is the best way to re-raise any exception with a message
supplemented with additional information (e.g. line number in a
template)? Let's say for simplicity I just want to add "sorry" to every
exception message. My naive solution was this:

try:
...
except Exception, e:
raise e.__class__, str(e) + ", sorry!"

This works pretty well for most exceptions, e.g.

... 1/0
... except Exception, e:
... raise e.__class__, str(e) + ", sorry!"
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
ZeroDivisionError: integer division or modulo by zero, sorry!

But it fails for some exceptions that cannot be instantiated with a
single string argument, like UnicodeDecodeError which gets "converted"
to a TypeError:

... unicode('\xe4')
... except Exception, e:
... raise e.__class__, str(e) + ", sorry!"
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
TypeError: function takes exactly 5 arguments (1 given)

Another approach is using a wrapper Extension class:

class SorryEx(Exception):
def __init__(self, e):
self._e = e
def __getattr__(self, name):
return getattr(self._e, name)
def __str__(self):
return str(self._e) + ", sorry!"

try:
unicode('\xe4')
except Exception, e:
raise SorryEx(e)

But then I get the name of the wrapper class in the message:

__main__.SorryEx: 'ascii' codec can't decode byte 0xe4 in position 0:
ordinal not in range(128), sorry!

Yet another approach would be to replace the __str__ method of e, but
this does not work for new style Exceptions (Python 2.5).

Any suggestions?

-- Chris

If you are sure that the exception isn't caught on another level just
use the following showtraceback() function, manipulate it's output
slightly and terminate your program with sys.exit()

def showtraceback():
'''
(Copied from code.py)
'''
try:
type, value, tb = sys.exc_info()
sys.last_type = type
sys.last_value = value
sys.last_traceback = tb
tblist = traceback.extract_tb(tb)
del tblist[:1]
lst = traceback.format_list(tblist)
if lst:
lst.insert(0, "Traceback (most recent call last):\n")
lst[len(lst):] = traceback.format_exception_only(type,
value)
finally:
tblist = tb = None
sys.stderr.write("".join(lst))
 
C

Christoph Zwerschke

Kay said:
If you are sure that the exception isn't caught on another level just
use the following showtraceback() function, manipulate it's output
slightly and terminate your program with sys.exit()

That's what I want to avoid. In my case the error is displayed and
evaluated in a completly different piece of software.

-- Chris
 
C

Christoph Zwerschke

Seems that no simple solution exists,
so for now, I will be using something like this:

class PoliteException(Exception):
def __init__(self, e):
self._e = e
def __getattr__(self, name):
return getattr(self._e, name)
def __str__(self):
if isinstance(self._e, PoliteException):
return str(self._e)
else:
return '\n%s: %s, I am sorry!' % (
self._e.__class__.__name__, str(self._e))

try:
unicode('\xe4')
except Exception, e:
raise PoliteException(e)
 
A

Alex Popescu

That's what I want to avoid. In my case the error is displayed and
evaluated in a completly different piece of software.

-- Chris

Probably the simplest solution would be to create a new exception and
wrapping the old one and the additional info. Unfortunately,
this may have a huge impact on 3rd party code that was catching the
original exception.

So, I think you should create an utility factory-like function that is
either creating a new exception instance as the one caught
and with the additional information, or an utility that knows how to
modify the caught exception according to its type.
In the first case you will need somehow to tell to the new instance
exception the real stack trace, because by simply raising
a new one the original stack trace may get lost.

bests,

../alex
 
C

Christoph Zwerschke

Sorry for the soliloquy, but what I am really using is the following so
that the re-raised excpetion has the same type:

def PoliteException(e):
class PoliteException(e.__class__):
def __init__(self, e):
self._e = e
def __getattr__(self, name):
return getattr(self._e, name)
def __str__(self):
if isinstance(self._e, PoliteException):
return str(self._e)
else:
return '\n%s: %s, I am sorry!' % (
self._e.__class__.__name__, str(self._e))
return PoliteException(e)

try:
unicode('\xe4')
except Exception, e:
raise PoliteException(e)
 
C

Christoph Zwerschke

Alex said:
Probably the simplest solution would be to create a new exception and
wrapping the old one and the additional info. Unfortunately, this
may have a huge impact on 3rd party code that was catching the
original exception. So, I think you should create an utility
factory-like function that is either creating a new exception
instance as the one caught and with the additional information,

Right, I have gone with that (see the example with the PoliteException
class somewhere below).
or an utility that knows how to modify the caught exception according
to its type.

I guess you mean something like this (simplified):

except Exception, e:
if getattr(e, 'reason'):
e.reason += "sorry"
else:
e.message += "sorry"

The problem is that these attribute names are not standardized and can
change between Python versions. Not even "args" is sure, and if a class
has "message" it does not mean that it is displayed. Therefore I think
the first approach is better.
In the first case you will need somehow to tell to the new instance
exception the real stack trace, because by simply raising
a new one the original stack trace may get lost.

Yes, but thats a different problem that is easy to solve.

-- Chris
 
A

Alex Popescu

Right, I have gone with that (see the example with the PoliteException
class somewhere below).


I guess you mean something like this (simplified):

except Exception, e:
if getattr(e, 'reason'):
e.reason += "sorry"
else:
e.message += "sorry"

The problem is that these attribute names are not standardized and can
change between Python versions. Not even "args" is sure, and if a class
has "message" it does not mean that it is displayed. Therefore I think
the first approach is better.


Yes, but thats a different problem that is easy to solve.

Yeah maybe for a python guy, but I am a newbie. I would really
appreciate if you can show in this thread how this can be done in
Python.

tia,

../alex
 
N

Neil Cerutti

Yeah maybe for a python guy, but I am a newbie. I would really
appreciate if you can show in this thread how this can be done
in Python.

Chech out the docs for sys.exc_info(), and for the raise
statement.

When handling an exception, you can rethrow a different
exception, but with the same traceback, by using the three-arg
version of raise.

See one of my earlier posts in this thread for a working example
(although it didn't solve Chris's problem).
 
G

Gerard Flanagan

Sorry for the soliloquy, but what I am really using is the following so
that the re-raised excpetion has the same type:

def PoliteException(e):
class PoliteException(e.__class__):
def __init__(self, e):
self._e = e
def __getattr__(self, name):
return getattr(self._e, name)
def __str__(self):
if isinstance(self._e, PoliteException):
return str(self._e)
else:
return '\n%s: %s, I am sorry!' % (
self._e.__class__.__name__, str(self._e))
return PoliteException(e)

try:
unicode('\xe4')
except Exception, e:
raise PoliteException(e)

Would a decorator work here?


class PoliteException(Exception):
def __init__(self, e):
self._e = e
def __getattr__(self, name):
return getattr(self._e, name)
def __str__(self):
return '\n%s: %s, I am sorry!' % (
self._e.__class__.__name__, str(self._e))

def politefail(fn):
def wrapper(*args, **kwargs):
try:
return fn(*args, **kwargs)
except Exception, e:
raise PoliteException(e)
return wrapper

@politefail
def funktion():
unicode('\xe4')

funktion()

@politefail
def raise_exception(err, *args):
raise err(*args)


def funktion():
if 1 != 2:
raise_exception(ArithmeticError, '1 is not equal to 2.')

print
funktion()
 
C

Christoph Zwerschke

Gerard said:
Would a decorator work here?

Depends on how you want to use that functionality. In my use case I only
need to catch the excpetion once.

Note that in your code the exception has not the right type which is
what I targeted in my last posting. I.e. the following will raise an
Exception if function is decorated:

try:
print funktion()
except ArithmeticError:
pass

-- Chris
 
S

samwyse

What is the best way to re-raise any exception with a message
supplemented with additional information (e.g. line number in a
template)? Let's say for simplicity I just want to add "sorry" to every
exception message. My naive solution was this:

try:
...
except Exception, e:
raise e.__class__, str(e) + ", sorry!"

This works pretty well for most exceptions, e.g.

... 1/0
... except Exception, e:
... raise e.__class__, str(e) + ", sorry!"
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
ZeroDivisionError: integer division or modulo by zero, sorry!

But it fails for some exceptions that cannot be instantiated with a
single string argument, like UnicodeDecodeError which gets "converted"
to a TypeError:

... unicode('\xe4')
... except Exception, e:
... raise e.__class__, str(e) + ", sorry!"
...
Traceback (most recent call last):
File "<stdin>", line 4, in <module>
TypeError: function takes exactly 5 arguments (1 given)

Another approach is using a wrapper Extension class:

class SorryEx(Exception):
def __init__(self, e):
self._e = e
def __getattr__(self, name):
return getattr(self._e, name)
def __str__(self):
return str(self._e) + ", sorry!"

try:
unicode('\xe4')
except Exception, e:
raise SorryEx(e)

But then I get the name of the wrapper class in the message:

__main__.SorryEx: 'ascii' codec can't decode byte 0xe4 in position 0:
ordinal not in range(128), sorry!

Yet another approach would be to replace the __str__ method of e, but
this does not work for new style Exceptions (Python 2.5).

Any suggestions?

-- Chris

Can "try" statements be used in "except" clauses? It appears so, thus
a hybrid approach might work well enough.

try:
...
except Exception, e:
try:
raise e.__class__, str(e) + ", sorry!"
except TypeError:
raise SorryEx(e)

That leaves the issue of the name being changed for
UnicodeDecodeError, which might be fixable by diddling with __name__
properties. Or perhaps SorryEx needs to be a factory that returns
exception classes; the last line would be "SorryEx(e)()". I'll have
to play with this a bit.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top