Python Basics #19 — Errors and Exception Handling

7 min read

Today we’ll cover errors and exception handling.

Imagine you write a program for a friend, and when they run it, it terminates with a cryptic error message. Someone who doesn’t know programming would be completely lost. When a program ends abnormally with an error, we usually say it crashed. To prevent this, you should always wrap code that might raise an error with a try / except block to handle exceptions.

Python’s exception-handling syntax has the following basic structure:

Basic exception-handling structure
try:
    ...
except:
    ...
else:
    ...
finally:
    ...
KeywordDescription
tryThe block where the main code runs.
exceptRuns when an error occurs.
elseRuns when no error occurs.
finallyRuns at the end, no matter what.

Recap of the structure:

  • try — put the main code you want to run here.
  • except — runs when an error occurs.
  • else — runs when no error occurs.
  • finally — always runs at the end, whether or not an error occurred.

The code below asks the user for two numbers and prints the result of dividing the first by the second. Let’s run it:

Python code
num_1 = int(input('First number: '))
num_2 = int(input('Second number: '))
result = num_1 / num_2
print('{} / {} = {}'.format(num_1, num_2, result))
Console output
First number: 4
Second number: 2
4 / 2 = 2.0

It runs fine with 4 and 2. But what if the user enters a non-number?

Let’s run it and enter one:

Console output
First number: one
Traceback (most recent call last):
  File "/Users/curtis/Dev/work/function.py", line 1, in <module>
    num_1 = int(input('First number: '))
ValueError: invalid literal for int() with base 10: 'one'

A string can’t be converted to an integer, so the program crashed with an error. To prevent this, you handle the exception. The basic exception-handling pattern uses try and except.

Let’s add it. Put the main code in a try block. Then use except to catch errors. After except, write the name of the exception class to catch. In the error message above you can see ValueError — write that name after except (line 6 in the snippet below). In the except body, write the code to run when that error occurs.

Python code
try:
    num_1 = int(input('First number: '))
    num_2 = int(input('Second number: '))
    result = num_1 / num_2
    print('{} / {} = {}'.format(num_1, num_2, result))
except ValueError as error:
    print('A "ValueError" occurred.')
    print(error)
Console output
First number: one
A "ValueError" occurred.
invalid literal for int() with base 10: 'one'

The error happened, but it was caught — the program didn’t crash and exited normally.

Forcing the user to re-run the whole program every time they make a mistake is annoying. Let’s modify the code so that on error, the user is prompted again:

Python code
while True:
    try:
        num_1 = int(input('First number: '))
        num_2 = int(input('Second number: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
        break
    except ValueError as error:
        print('That value is not a number. Please try again.')
Console output
First number: one
That value is not a number. Please try again.
First number: 4
Second number: 2
4 / 2 = 2.0

Now let’s look at an error other than ValueError. A common beginner mistake is dividing by zero. In math you might say division by zero is undefined (or treat it as zero), but in programming it raises a ZeroDivisionError. Let’s enter 0 to trigger it:

Console output
First number: 4
Second number: 0
Traceback (most recent call last):
  File "/Users/curtis/Dev/work/function.py", line 5, in <module>
    result = num_1 / num_2
ZeroDivisionError: division by zero

You can use multiple except clauses to handle multiple exception types. Let’s add one for ZeroDivisionError:

Python code
while True:
    try:
        num_1 = int(input('First number: '))
        num_2 = int(input('Second number: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
        break
    except ValueError as error:
        print('That value is not a number. Please try again.')
    except ZeroDivisionError as error:
        print("Can't divide by zero. Please try again.")
Console output
First number: one
That value is not a number. Please try again.
First number: 4
Second number: 0
Can't divide by zero. Please try again.
First number: 4
Second number: 2
4 / 2 = 2.0

Both errors are handled correctly.

ValueError and ZeroDivisionError are names of Python’s built-in exception classes. The full list is in the official docs:

Reference: Python built-in exceptions

It’s hard to anticipate every error. Sometimes you need a fallback that catches anything not specifically handled.

Let’s add some code that triggers a NameError:

Python code
while True:
    try:
        num_1 = int(input('First number: '))
        num_2 = int(input('Second number: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
        print(bad_variable)  # reference an undefined variable
        break
    except ValueError as error:
        print('That value is not a number. Please try again.')
    except ZeroDivisionError as error:
        print("Can't divide by zero. Please try again.")
Console output
First number: 4
Second number: 2
4 / 2 = 2.0
Traceback (most recent call last):
  File "/Users/curtis/Dev/work/function.py", line 7, in <module>
    print(bad_variable)
NameError: name 'bad_variable' is not defined

To handle this case, add a generic catch-all using the base Exception class:

Python code
while True:
    try:  # reference an undefined variable
        num_1 = int(input('First number: '))
        num_2 = int(input('Second number: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
        print(bad_variable)
        break
    except ValueError as error:
        print('That value is not a number. Please try again.')
    except ZeroDivisionError as error:
        print("Can't divide by zero. Please try again.")
    except Exception:
        print('Some other error occurred. 🥲')
        break
Console output
First number: 4
Second number: 2
4 / 2 = 2.0
Some other error occurred. 🥲

When you use Exception, it must come last. Otherwise it will catch every error before any specific handler can run. Let’s move it to the top to demonstrate:

Python code
while True:
    try:  # reference an undefined variable
        num_1 = int(input('First number: '))
        num_2 = int(input('Second number: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
        print(bad_variable)
        break
    except Exception:
        print('Some other error occurred. 🥲')
        break
    except ValueError as error:
        print('That value is not a number. Please try again.')
    except ZeroDivisionError as error:
        print("Can't divide by zero. Please try again.")
Console output
First number: one   
Some other error occurred. 🥲

Even though one should trigger ValueError, the Exception block ran instead — because it’s matched first.

Now let’s use else. Place an else block after the try and except blocks, and put the code you want to run only when no error occurred:

Python code
while True:
    try:  # reference an undefined variable
        num_1 = int(input('First number: '))
        num_2 = int(input('Second number: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
        break
    except ValueError as error:
        print('That value is not a number. Please try again.')
    except ZeroDivisionError as error:
        print("Can't divide by zero. Please try again.")
    except Exception:
        print('Some other error occurred. 🥲')
    else:
        print('No error occurred.')
Console output
First number: 4
Second number: 2
4 / 2 = 2.0

The else block didn’t run, even though no error occurred. Why? Because when the try block runs successfully, break exits the while loop before else can run. Move break into the else block:

Python code
while True:
    try:  # reference an undefined variable
        num_1 = int(input('First number: '))
        num_2 = int(input('Second number: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
    except ValueError as error:
        print('That value is not a number. Please try again.')
    except ZeroDivisionError as error:
        print("Can't divide by zero. Please try again.")
    except Exception:
        print('Some other error occurred. 🥲')
    else:
        print('No error occurred.')
        break
Python code
First number: 4
Second number: 2
4 / 2 = 2.0
No error occurred.

Now else runs.

Finally, finally. The finally block always runs at the end, regardless of whether an error occurred. Define an attempt variable outside the while loop and count loop iterations:

Python code
attempt = 0

while True:
    try:  # reference an undefined variable
        num_1 = int(input('First number: '))
        num_2 = int(input('Second number: '))
        result = num_1 / num_2
        print('{} / {} = {}'.format(num_1, num_2, result))
    except ValueError as error:
        print('That value is not a number. Please try again.')
    except ZeroDivisionError as error:
        print("Can't divide by zero. Please try again.")
    except Exception:
        print('Some other error occurred. 🥲')
    else:
        print('No error occurred.')
        break
    finally:
        attempt += 1
        print('Attempts: {}'.format(attempt))
Console output
First number: one
That value is not a number. Please try again.
Attempts: 1
First number: 4
Second number: 0
Can't divide by zero. Please try again.
Attempts: 2
First number: 4
Second number: 2
4 / 2 = 2.0
No error occurred.
Attempts: 3

The finally block runs every iteration, as expected.

That wraps up this lesson. In the next lesson we’ll cover modules and packages. Nice work! 👍

X