Kit
Kit

Reputation: 31543

How do I raise the same Exception with a custom message in Python?

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)

Upvotes: 304

Views: 232947

Answers (16)

Ben
Ben

Reputation: 13858

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.

Upvotes: 376

Johan Lundberg
Johan Lundberg

Reputation: 27088

Python 2.x:

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

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].


The above does not work with python 3 since .message is not an attribute of ValueError. Instead you could use this:

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))

Depending on what the purpose is, you can also add 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)

Upvotes: 130

wim
wim

Reputation: 363586

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

Modify the args attribute, which is used by BaseException.__str__ to render an exception. 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.

Upvotes: 29

Jose Rondon
Jose Rondon

Reputation: 390

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}')

Upvotes: -1

stam
stam

Reputation: 349

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

Upvotes: -2

shrewmouse
shrewmouse

Reputation: 6050

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')

Upvotes: 17

hnsanadhya
hnsanadhya

Reputation: 59

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.

Upvotes: 2

RobinL
RobinL

Reputation: 11597

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)

Upvotes: 1

Alexey Antonenko
Alexey Antonenko

Reputation: 2637

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

Upvotes: 2

Chris Johnson
Chris Johnson

Reputation: 22026

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?

Upvotes: 14

user3761308
user3761308

Reputation: 791

Python 3 built-in exceptions have the strerror field:

except ValueError as err:
  err.strerror = "New error message"
  raise err

Upvotes: 1

Bryce Guinta
Bryce Guinta

Reputation: 3752

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)

Upvotes: 4

eumiro
eumiro

Reputation: 213125

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'

Upvotes: 7

Zitrax
Zitrax

Reputation: 20344

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 )

Upvotes: 1

Ruggero Turra
Ruggero Turra

Reputation: 17740

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

Upvotes: 8

igni
igni

Reputation: 134

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

Upvotes: -7

Related Questions