Python Basics #19 — Errors and Exception Handling
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:
try:
...
except:
...
else:
...
finally:
...| Keyword | Description |
|---|---|
try | The block where the main code runs. |
except | Runs when an error occurs. |
else | Runs when no error occurs. |
finally | Runs 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:
num_1 = int(input('First number: '))
num_2 = int(input('Second number: '))
result = num_1 / num_2
print('{} / {} = {}'.format(num_1, num_2, result))First number: 4
Second number: 2
4 / 2 = 2.0It runs fine with 4 and 2. But what if the user enters a non-number?
Let’s run it and enter one:
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.
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)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:
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.')First number: one
That value is not a number. Please try again.
First number: 4
Second number: 2
4 / 2 = 2.0Now 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:
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 zeroYou can use multiple except clauses to handle multiple exception types. Let’s add one for ZeroDivisionError:
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.")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.0Both 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:
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.")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 definedTo handle this case, add a generic catch-all using the base Exception class:
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. 🥲')
breakFirst 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:
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.")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:
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.')First number: 4
Second number: 2
4 / 2 = 2.0The 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:
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.')
breakFirst 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:
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))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: 3The finally block runs every iteration, as expected.
That wraps up this lesson. In the next lesson we’ll cover modules and packages. Nice work! 👍