On , I learnt ...

It’s better to explicitly chain Python exceptions

When converting one type of exception to another in an except block, it’s best to explicitly chain them using the from keyword.

That is, prefer this:

def eat_lunch():
    try:
        make_cheese_on_toast()
    except NoCheese as e:
        raise UnableToEat("Need to go shopping") from e

to:

def eat_lunch():
    try:
        make_cheese_on_toast()
    except NoCheese:
        raise UnableToEat("Need to go shopping")

The former will set the __cause__ attribute on the newly raised UnableToEat exception, indicating that the NoCheese exception was a direct cause.

This leads to a better stack trace message where the two stack traces are combined with a sentence indicating one was a “direct cause” of the other:

Traceback (most recent call last):
  File "lunch.py", line 10, in eat_lunch
    make_cheese_on_toast()
  File "lunch.py", line 5, in make_cheese_on_toast
    raise NoCheese
__main__.NoCheese

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

Traceback (most recent call last):
  File "lunch.py", line 15, in <module>
    eat_lunch()
  File "lunch.py", line 12, in eat_lunch
    raise UnableToEat("Need to go shopping") from e
__main__.UnableToEat: Need to go shopping

Without the explicit chaining, the joining message would be:

During handling of the above exception, another exception occurred