260

I have this try block in my code:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise ValueError(errmsg)

Strictly speaking, I am actually raising another ValueError, not the ValueError thrown by do_something...(), which is referred to as err in this case. How do I attach a custom message to err? I try the following code but fails due to err, a ValueError instance, not being callable:

try:
    do_something_that_might_raise_an_exception()
except ValueError as err:
    errmsg = 'My custom error message.'
    raise err(errmsg)
Kit
  • 30,365
  • 39
  • 105
  • 149
  • 15
    @Hamish, attaching additional information and re-raising exceptions can be very helpful when debugging. – Johan Lundberg Feb 06 '12 at 09:39
  • @Johan Absolutely - and that's what a stacktrace is for. Can't quite understand why you'd edit the existing error message instead of raising a new error. – Hamish Feb 06 '12 at 21:18
  • @Hamish. Sure but you can add other stuff. For your question, have a look at my answer and the example of UnicodeDecodeError. If you have comments on that perhaps comment my answer instead. – Johan Lundberg Feb 06 '12 at 21:24
  • Possible duplicate of [Adding information to an exception?](http://stackoverflow.com/questions/6062576/adding-information-to-an-exception) – Trevor Boyd Smith Mar 17 '17 at 17:36
  • 3
    @Kit it is 2020 and python 3 is everywhere. Why don't change the accepted answer to Ben's answer :-) – mit Jun 17 '20 at 10:47
  • Related (not dupe): [Re-raise exception with a different type and message, preserving existing information](https://stackoverflow.com/q/696047/674039) – wim Feb 23 '23 at 19:17
  • 1
    @mit It's now 2023 and I've changed the correct answer :D – Kit Mar 27 '23 at 13:37

16 Answers16

331

If you're lucky enough to only support python 3.x, this really becomes a thing of beauty :)

raise from

We can chain the exceptions using raise from.

try:
    1 / 0
except ZeroDivisionError as e:
    raise Exception('Smelly socks') from e

In this case, the exception your caller would catch has the line number of the place where we raise our exception.

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

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

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    raise Exception('Smelly socks') from e
Exception: Smelly socks

Notice the bottom exception only has the stacktrace from where we raised our exception. Your caller could still get the original exception by accessing the __cause__ attribute of the exception they catch.

with_traceback

Or you can use with_traceback.

try:
    1 / 0
except ZeroDivisionError as e:
    raise Exception('Smelly socks').with_traceback(e.__traceback__)

Using this form, the exception your caller would catch has the traceback from where the original error occurred.

Traceback (most recent call last):
  File "test.py", line 2, in <module>
    1 / 0
ZeroDivisionError: division by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "test.py", line 4, in <module>
    raise Exception('Smelly socks').with_traceback(e.__traceback__)
  File "test.py", line 2, in <module>
    1 / 0
Exception: Smelly socks

Notice the bottom exception has the line where we performed the invalid division as well as the line where we reraise the exception.

mit
  • 11,083
  • 11
  • 50
  • 74
Ben
  • 13,160
  • 5
  • 19
  • 12
  • 6
    Is it possible to add a custom message to an exception without the additional traceback? For example, can `raise Exception('Smelly socks') from e` be modified to just add "Smelly socks" as a comment to the original traceback rather than introducing a new traceback of its own. – joelostblom Jul 06 '16 at 23:34
  • That's the behavior you'll get from Johan Lundberg's answer – Ben Jul 07 '16 at 14:45
  • 7
    Re-raising a new exception or chain raising exceptions with new messages creates more confusion than needed in many cases. By itself exceptions are complex to handle. A better strategy is to just append your message to the argument of the original exception if possible as in err.args += ("message",) and re-raise the exception message. The traceback might not take you to the line numbers where the exception was caught but it will take you to where the exception occurred for sure. – user-asterix Jan 26 '18 at 23:53
  • 3
    You can also explicitly suppress the _display_ of the exception chain by specifying None in the from clause: `raise RuntimeError("Something bad happened") from None` – pfabri May 29 '20 at 12:04
  • 11
    **This fails to answer the actual question.** Yes, we all know how to chain Python 3.x exceptions in 2020. The actual question is how to modify the original exception message of the original exception *without* chaining or other irrelevant shenanigans that only raise new exceptions and thus obstruct the original traceback. – Cecil Curry Jul 31 '20 at 05:39
125

Update: For Python 3, check Ben's answer

Update 2023: I wrote this answer more than ten years ago and there are better answers now. You should be using python 3 and the answer above.

Original answer:

To attach a message to the current exception and re-raise it: (the outer try/except is just to show the effect)

For python 2.x where x>=6:

try:
    try:
      raise ValueError  # something bad...
    except ValueError as err:
      err.message=err.message+" hello"
      raise              # re-raise current exception
except ValueError as e:
    print(" got error of type "+ str(type(e))+" with message " +e.message)

This will also do the right thing if err is derived from ValueError. For example UnicodeDecodeError.

Note that you can add whatever you like to err. For example err.problematic_array=[1,2,3].


Edit: @Ducan points in a comment the above does not work with python 3 since .message is not a member of ValueError. Instead you could use this (valid python 2.6 or later or 3.x):

try:
    try:
      raise ValueError
    except ValueError as err:
       if not err.args: 
           err.args=('',)
       err.args = err.args + ("hello",)
       raise 
except ValueError as e:
    print(" error was "+ str(type(e))+str(e.args))

Edit2:

Depending on what the purpose is, you can also opt for adding the extra information under your own variable name. For both python2 and python3:

try:
    try:
      raise ValueError
    except ValueError as err:
       err.extra_info = "hello"
       raise 
except ValueError as e:
    print(" error was "+ str(type(e))+str(e))
    if 'extra_info' in dir(e):
       print e.extra_info
Johan Lundberg
  • 26,184
  • 12
  • 71
  • 97
  • 12
    Since you've gone to the effort of using Python 3 style exception handling and `print`, you should probably note that your code doesn't work in Python 3.x as there is no `message` attribute on exceptions. `err.args = (err.args[0] + " hello",) + err.args[1:]` may work more reliably (and then just convert to a string to get the message). – Duncan Feb 06 '12 at 09:50
  • The answer is now updated addressing both python>=2.6 and python 3. – Johan Lundberg Feb 07 '12 at 10:16
  • 1
    Unfortunately there's no guarantee that args[0] is a string type representing an error message - "The tuple of arguments given to the exception constructor. Some built-in exceptions (like IOError) expect a certain number of arguments and assign a special meaning to the elements of this tuple, while others are usually called only with a single string giving an error message.". So the code won't work arg[0] is not an error message (it could be an int, or it could be a string representing a file name). – Trent Nov 01 '13 at 05:29
  • 1
    @Taras, Interesting. Do you have a reference on that? Then I would add to a completely new member: err.my_own_extra_info. Or encapsulate it all in my own exception keeping the new and the original information. – Johan Lundberg Nov 01 '13 at 08:20
  • 1
    @JohanLundberg, the quoted section comes from the Python docs - http://docs.python.org/2/library/exceptions.html. – Trent Nov 05 '13 at 22:48
  • 3
    A real example of when args[0] isn't an error message - http://docs.python.org/2/library/exceptions.html - "exception EnvironmentError The base class for exceptions that can occur outside the Python system: IOError, OSError. When exceptions of this type are created with a 2-tuple, the first item is available on the instance’s errno attribute (it is assumed to be an error number), and the second item is available on the strerror attribute (it is usually the associated error message). The tuple itself is also available on the args attribute." – Trent Nov 05 '13 at 22:51
  • 1
    What's up with those semicolons after `raise ValueError` ? – Niels Bom Dec 23 '15 at 16:58
  • @NielsBom Python allows you to delimit multiple statements on one line with semicolons. Since there is only whitespace after the semicolon in this case, it does nothing. You can raise an exception with no arguments without the semicolon. Maybe he intented to make clear that this exception has no message/args – Bryce Guinta Jul 28 '16 at 19:18
  • 2
    I don't understand this at all. The only reason setting the `.message` attribute does anything here is that this attribute is *explicitly* printed. If you were to raise the exception without catching and printing, you would *not* see the `.message` attribute do anything useful. – DanielSank Aug 30 '16 at 19:58
  • 1
    your first code snippet doesn't work for me. the message doesn't get modified for me. same behavior as: http://stackoverflow.com/q/6062576/52074 – Trevor Boyd Smith Mar 17 '17 at 17:35
  • **For Python 3, [just check shrewmouse's answer](https://stackoverflow.com/a/62662138/2809027).** Unlike [Ben's non-answer cited in this answer's preamble](https://stackoverflow.com/a/29442282/2809027), [shrewmouse's answer](https://stackoverflow.com/a/62662138/2809027) actually addresses the original question, preserves the original traceback, *and* is pure-Python 3.x. It's also mildly funny. (In 2020, we take what we can get, people.) – Cecil Curry Jul 31 '20 at 06:42
  • I'm having trouble implementing whats suggested here. Using the first suggestion, I couldn't add newlines in this message type and have them formatted properly. When using the second suggestion, you have to specifically print the extra_info, and that doesn't reraise the exception. – openCivilisation Apr 24 '23 at 00:24
15

This only works with Python 3. You can modify the exception's original arguments and add your own arguments.

An exception remembers the args it was created with. I presume this is so that you can modify the exception.

In the function reraise we prepend the exception's original arguments with any new arguments that we want (like a message). Finally we re-raise the exception while preserving the trace-back history.

def reraise(e, *args):
  '''re-raise an exception with extra arguments
  :param e: The exception to reraise
  :param args: Extra args to add to the exception
  '''

  # e.args is a tuple of arguments that the exception with instantiated with.
  #
  e.args = args + e.args

  # Recreate the exception and preserve the traceback info so that we can see 
  # where this exception originated.
  #
  raise e.with_traceback(e.__traceback__)   


def bad():
  raise ValueError('bad')

def very():
  try:
    bad()
  except Exception as e:
    reraise(e, 'very')

def very_very():
  try:
    very()
  except Exception as e:
    reraise(e, 'very')

very_very()

output

Traceback (most recent call last):
  File "main.py", line 35, in <module>
    very_very()
  File "main.py", line 30, in very_very
    reraise(e, 'very')
  File "main.py", line 15, in reraise
    raise e.with_traceback(e.__traceback__)
  File "main.py", line 28, in very_very
    very()
  File "main.py", line 24, in very
    reraise(e, 'very')
  File "main.py", line 15, in reraise
    raise e.with_traceback(e.__traceback__)
  File "main.py", line 22, in very
    bad()
  File "main.py", line 18, in bad
    raise ValueError('bad')
ValueError: ('very', 'very', 'bad')
Jeff Widman
  • 22,014
  • 12
  • 72
  • 88
shrewmouse
  • 5,338
  • 3
  • 38
  • 43
  • 3
    **By far and away the best answer.** This is the only answer that answers the original question, preserves the original traceback, *and* is pure-Python 3.x. Mad props for also banging that "very, very bad" meme drum. Humour is an unquestionably good thing – especially in otherwise dry, technical answers like this. *Bravo!* – Cecil Curry Jul 31 '20 at 06:22
  • @CecilCurry Opinions differ. I don't like the way this puts more junk into the traceback, that frame from line 15 ought to be hidden (as it would have been if a bare `raise` were used instead of a function call into `reraise`). It is strange to use `e.with_traceback(e.__traceback__)` i.e. passing its _own_ traceback. Usually that method is used to provide the traceback with some different exception instance. Ultimately I think this `reraise` helper function harms the readability of the traceback significantly. Check [new answer](https://stackoverflow.com/a/75549200/674039) for Python 3.11+. – wim Feb 23 '23 at 19:07
13

It seems all the answers are adding info to e.args[0], thereby altering the existing error message. Is there a downside to extending the args tuple instead? I think the possible upside is, you can leave the original error message alone for cases where parsing that string is needed; and you could add multiple elements to the tuple if your custom error handling produced several messages or error codes, for cases where the traceback would be parsed programmatically (like via a system monitoring tool).

## Approach #1, if the exception may not be derived from Exception and well-behaved:

def to_int(x):
    try:
        return int(x)
    except Exception as e:
        e.args = (e.args if e.args else tuple()) + ('Custom message',)
        raise

>>> to_int('12')
12

>>> to_int('12 monkeys')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')

or

## Approach #2, if the exception is always derived from Exception and well-behaved:

def to_int(x):
    try:
        return int(x)
    except Exception as e:
        e.args += ('Custom message',)
        raise

>>> to_int('12')
12

>>> to_int('12 monkeys')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in to_int
ValueError: ("invalid literal for int() with base 10: '12 monkeys'", 'Custom message')

Can you see a downside to this approach?

Chris Johnson
  • 20,650
  • 6
  • 81
  • 80
10

Python 3.11+

PEP 678 – Enriching Exceptions with Notes was accepted and landed in Python 3.11. New APIs allow users to attach custom message(s) to existing errors. This is useful for adding additional context when an error is encountered.

Using the add_note method is suitable for answering the original question:

try:
    int("eleven")
except ValueError as e:
    errmsg = "My custom error message."
    e.add_note(errmsg)
    raise

It would render like this:

Traceback (most recent call last):
  File "/tmp/example.py", line 2, in <module>
    int("eleven")
ValueError: invalid literal for int() with base 10: 'eleven'
My custom error message.

Python < 3.11

Modifying the args attribute, which is used by BaseException.__str__ to render an exception, is the only way. You could either extend the args:

try:
    int("eleven")
except ValueError as e:
    errmsg = "My custom error message."
    e.args += (errmsg,)
    raise e

Which will render as:

Traceback (most recent call last):
  File "/tmp/example.py", line 2, in <module>
    int("eleven")
ValueError: ("invalid literal for int() with base 10: 'eleven'", 'My custom error message.')

Or you could replace the args[0], which is a little more complicated but produces a cleaner result.

try:
    int("eleven")
except ValueError as e:
    errmsg = "My custom error message."
    args = e.args
    if not args:
        arg0 = errmsg
    else:
        arg0 = f"{args[0]}\n{errmsg}"
    e.args = (arg0,) + args[1:]
    raise

This will render the same way as the Python 3.11+ exception __notes__ do:

Traceback (most recent call last):
  File "/tmp/example.py", line 2, in <module>
    int("eleven")
ValueError: invalid literal for int() with base 10: 'eleven'
My custom error message.
wim
  • 338,267
  • 99
  • 616
  • 750
7
try:
    try:
        int('a')
    except ValueError as e:
        raise ValueError('There is a problem: {0}'.format(e))
except ValueError as err:
    print err

prints:

There is a problem: invalid literal for int() with base 10: 'a'
bignose
  • 30,281
  • 14
  • 77
  • 110
eumiro
  • 207,213
  • 34
  • 299
  • 261
  • 2
    I was wondering if there was a Python idiom for what I'm trying to do, other than raising _another_ instance. – Kit Feb 06 '12 at 08:20
  • @Kit - I would call it 're-raising an exception': http://docs.python.org/reference/simple_stmts.html#raise – eumiro Feb 06 '12 at 08:24
  • 1
    @eumiro, No you are making a new exception. See my answer. From your link: "... but raise with no expressions should be preferred if the exception to be re-raised was the most recently active exception in the current scope." – Johan Lundberg Feb 06 '12 at 08:39
  • 3
    @JohanLundberg - `raise` without parameters is re-raising. If OP wants to add a message, he has to raise a new exception and can re-use the message/type of the original exception. – eumiro Feb 06 '12 at 08:47
  • 2
    If you want to *add* a message you can not create a new message from scratch by throwing "ValueError". By doing so, you destroy the underlying information of what kind of ValueError it is (similar to slicing in C++). By *re-throwing* the *same* exception with raise without an argument, you pass the original object with that correct specific type (derived from ValueError). – Johan Lundberg Mar 24 '15 at 18:27
6

This code template should allow you to raise an exception with a custom message.

try:
     raise ValueError
except ValueError as err:
    raise type(err)("my message")
octopusgrabbus
  • 10,555
  • 15
  • 68
  • 131
Ruggero Turra
  • 16,929
  • 16
  • 85
  • 141
  • 6
    This does not preserve the stack trace. – plok May 09 '14 at 11:31
  • 1
    The questioner does not specify that the stack trace be preserved. – shrewmouse Jun 29 '18 at 15:36
  • 1
    Don't be intentionally obtuse. The original question verbatim was: "How do I raise the same `Exception` with a custom message in Python?" This non-answer raises a *new* exception and thus fails to answer the original question at all. *This is why we can't have good things.* – Cecil Curry Jul 31 '20 at 05:46
  • 2
    @plok could use `raise type(err)("my message") from err` – timgeb Jun 09 '22 at 09:02
4

This is the function I use to modify the exception message in Python 2.7 and 3.x while preserving the original traceback. It requires six

def reraise_modify(caught_exc, append_msg, prepend=False):
    """Append message to exception while preserving attributes.

    Preserves exception class, and exception traceback.

    Note:
        This function needs to be called inside an except because
        `sys.exc_info()` requires the exception context.

    Args:
        caught_exc(Exception): The caught exception object
        append_msg(str): The message to append to the caught exception
        prepend(bool): If True prepend the message to args instead of appending

    Returns:
        None

    Side Effects:
        Re-raises the exception with the preserved data / trace but
        modified message
    """
    ExceptClass = type(caught_exc)
    # Keep old traceback
    traceback = sys.exc_info()[2]
    if not caught_exc.args:
        # If no args, create our own tuple
        arg_list = [append_msg]
    else:
        # Take the last arg
        # If it is a string
        # append your message.
        # Otherwise append it to the
        # arg list(Not as pretty)
        arg_list = list(caught_exc.args[:-1])
        last_arg = caught_exc.args[-1]
        if isinstance(last_arg, str):
            if prepend:
                arg_list.append(append_msg + last_arg)
            else:
                arg_list.append(last_arg + append_msg)
        else:
            arg_list += [last_arg, append_msg]
    caught_exc.args = tuple(arg_list)
    six.reraise(ExceptClass,
                caught_exc,
                traceback)
Bryce Guinta
  • 3,456
  • 1
  • 35
  • 36
  • This is one of the few answers *that actually answers the original question.* So, that's good. Sadly, Python 2.7 (and thus `six`) is EOL. So, that's bad. Although one could technically still use `six` in 2020, there's no tangible upside to doing so. Pure-Python 3.x solutions are vastly preferable now. – Cecil Curry Jul 31 '20 at 05:54
3

Either raise the new exception with your error message using

raise Exception('your error message')

or

raise ValueError('your error message')

within the place where you want to raise it OR attach (replace) error message into current exception using 'from' (Python 3.x supported only):

except ValueError as e:
  raise ValueError('your message') from e
Alexey Antonenko
  • 2,389
  • 1
  • 18
  • 18
3

Try below:

try:
    raise ValueError("Original message. ")
except Exception as err:
    message = 'My custom error message. '
    # Change the order below to "(message + str(err),)" if custom message is needed first. 
    err.args = (str(err) + message,)
    raise 

Output:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
      1 try:
----> 2     raise ValueError("Original message")
      3 except Exception as err:
      4     message = 'My custom error message.'
      5     err.args = (str(err) + ". " + message,)

ValueError: Original message. My custom error message.
hnsanadhya
  • 61
  • 1
  • 5
1

The current answer did not work good for me, if the exception is not re-caught the appended message is not shown.

But doing like below both keeps the trace and shows the appended message regardless if the exception is re-caught or not.

try:
  raise ValueError("Original message")
except ValueError as err:
  t, v, tb = sys.exc_info()
  raise t, ValueError(err.message + " Appended Info"), tb

( I used Python 2.7, have not tried it in Python 3 )

Zitrax
  • 19,036
  • 20
  • 88
  • 110
1

Python 3 built-in exceptions have the strerror field:

except ValueError as err:
  err.strerror = "New error message"
  raise err
user3761308
  • 744
  • 3
  • 14
  • 30
  • This doesn't seem to work. Are you missing somethng? – MasayoMusic Jul 13 '19 at 20:34
  • 4
    [The `strerror` instance variable is specific to the `OSError` exception](https://docs.python.org/3/library/exceptions.html#OSError.strerror). Since most `ValueError` exceptions are guaranteed *not* to define this variable, this non-solution typically raises non-human-readable exceptions and is thus actively harmful. *lol, bro.* – Cecil Curry Jul 31 '20 at 05:56
1

None of the above solutions did exactly what I wanted, which was to add some information to the first part of the error message i.e. I wanted my users to see my custom message first.

This worked for me:

exception_raised = False
try:
    do_something_that_might_raise_an_exception()
except ValueError as e:
    message = str(e)
    exception_raised = True

if exception_raised:
    message_to_prepend = "Custom text"
    raise ValueError(message_to_prepend + message)
RobinL
  • 11,009
  • 8
  • 48
  • 68
-1

I tried this compact version of @RobinL, and worked as well:

try:
    do_something_that_might_raise_an_exception()
except ValueError as e:
    error_message = 'My error message'
    raise ValueError(f'Custom text {error_message}')
Jose Rondon
  • 370
  • 2
  • 6
  • 13
-2

Raising same error, with prepending custom text message in front. (edit - sorry, actually same as https://stackoverflow.com/a/65494175/15229310 , why there is like 10 (upvoted) 'solutions' that simply don't answer question as posted?)

    try:
       <code causing exception>
    except Exception as e:
        e.args = (f"My custom text. Original Exception text: {'-'.join(e.args)}",)
        raise
stam
  • 175
  • 9
-7

if you want to custom the error type, a simple thing you can do is to define an error class based on ValueError.

igni
  • 134
  • 1
  • 9