Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Errors and catching them

There are two types of errors in Python. A few really bad errors are segfaults. These are almost always something triggered via the C interface (such as by using ctypes), and are not due to problems in your Python code. Most errors in Python are part of the language, called Exceptions.

An Exception is just a special control flow feature for things that are “exceptional”; often errors, but they are used for other things. In fact, internally, loops end by triggering a special exception!

Exceptions “bubble up” through the stack to the outermost scope. If you catch an exception before it reaches the top, you can handle it. If you don’t, then it shows up on the screen or in your logs as a traceback.

1 / 0
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
Cell In[1], line 1
----> 1 1 / 0

ZeroDivisionError: division by zero
try:
    1 / 0
except ZeroDivisionError:
    pass

Exceptions use inheritance to form a tree structure, so you can be as tight or as loose as needed in catching them. Let’s see the parents of Zero Division error:

ZeroDivisionError.__mro__
(ZeroDivisionError, ArithmeticError, Exception, BaseException, object)

You could catch any of these instead:

try:
    1 / 0
except ArithmeticError as e:
    print(repr(e))
ZeroDivisionError('division by zero')

Always catch the most narrow exception you can! Never try to catch a really broad class or all exceptions, because things like running out of memory, exit signals, and more are exceptions too, and you don’t want to catch those if you didn’t mean to!

Here’s a basic example of making your own:

class MyNewException(RuntimeError):
    pass


try:
    raise MyNewException()
except MyNewException:
    pass

There can be as many except blocks as you need, there’s an else block if you want something to run only if nothing was caught, and there’s also a finally block, which will always run, even if the exception is caught:

try:
    1 / 0
except ArithmeticError:
    print("Caught the exception!")
finally:
    print("I can run cleanup, regardless of what happens above!")
Caught the exception!
I can run cleanup, regardless of what happens above!

Where would you want something like this? How about closing a file!

try:
    f = open(...)
    # do stuff with f that might throw an exception (basically anything)
finally:
    f.close()

This way, if an exception is thrown, the file still gets nicely closed. In fact, this is so important, we’ll see a feature built around it soon!