Errors and Exceptions
What Are Errors?
Errors (also called exceptions) are problems that occur when your program runs. When Python encounters an error, it stops running and displays an error message.
Example:
age = int(input("Enter your age: "))
# If the user types "twenty" instead of "20", Python crashes with an error!
Output:
Enter your age: twenty
ValueError: invalid literal for int() with base 10: 'twenty'
Without error handling, your program crashes and stops. With error handling, you can catch these errors and keep your program running.
Handling errors with try and except
The try/except pattern lets you catch errors and handle them gracefully instead of crashing. There are parallels with the if/elif/else control flow patterns.
Basic Syntax
try:
# Code that might cause an error
risky_code()
except:
# What to do if ANY error occurs
print("Something went wrong!")
How it works:
- Python tries to run the code in the
tryblock - If an error occurs, it jumps to the
exceptblock - If no error occurs, the
exceptblock is skipped
Simple Example
# Without error handling - crashes!
age = int(input("Enter your age: "))
print(f"You are {age} years old")
Without error handling, if the user enters an invalid response, e.g. a string that can not be converted to an int, this throws an error and the whole program crashes and stops:
Enter your age: ten
...
ValueError: invalid literal for int() with base 10: 'ten'
With the try/except pattern, you can instead handle the error, e.g. by printing out a message and continuing.
# With error handling - doesn't crash!
age = int(input("Enter your age: "))
print(f"You are {age} years old")
# python first tries the code in the try block
try:
age = int(input("Enter your age: "))
print(f"You are {age} years old")
except:
print("That's not a valid age!")
This gives the output:
Enter your age: ten
That's not a valid age!
# Program continues running!
Common Types of Errors
Some common errors that are encountered with even simple Python code include:
ValueError- Invalid conversionsTypeError- Wrong data typesKeyError- Missing dictionary keysIndexError- Invalid list indicesFileNotFoundError- Missing filesZeroDivisionError- Division by zero
ValueError
Happens when you try to convert something to the wrong type.
age = int("twenty") # ValueError: can't convert "twenty" to integer
number = float("abc") # ValueError: can't convert "abc" to float
TypeError
Happens when you use the wrong type of data for an operation.
result = "5" + 5 # TypeError: can't add string and integer
name = "Alice"
name[0] = "B" # TypeError: strings are immutable
KeyError
Happens when you try to access a dictionary key that doesn’t exist.
flashcards = {"Dog": "Hund", "Cat": "Katze"}
translation = flashcards["Bird"] # KeyError: 'Bird' doesn't exist
IndexError
Happens when you try to access a list index that doesn’t exist.
scores = [85, 92, 78]
score = scores[5] # IndexError: list only has 3 items (indices 0-2)
FileNotFoundError
Happens when you try to open a file that doesn’t exist.
with open("missing_file.txt", "r") as file:
content = file.read() # FileNotFoundError: file doesn't exist
ZeroDivisionError
Happens when you try to divide by zero.
result = 10 / 0 # ZeroDivisionError: division by zero
Catching Specific Errors
It’s better to catch specific error types so you know exactly what went wrong.
Single Exception Type
try:
age = int(input("Enter your age: "))
print(f"You are {age} years old")
except ValueError:
print("Please enter a number, not text!")
Multiple Exception Types
try:
flashcards = {"Dog": "Hund", "Cat": "Katze"}
word = input("Enter a word: ")
translation = flashcards[word]
print(f"{word} = {translation}")
except KeyError:
print("That word isn't in the flashcards!")
except ValueError:
print("Invalid input format!")
Catching Multiple Errors with One Block
try:
result = 10 / int(input("Enter a number: "))
print(result)
except (ValueError, ZeroDivisionError):
print("Invalid input or division by zero!")
The else and finally clauses
In addition to try and except, else and finally are two additional keywords that can be used to further control error handling behaviour.
else - Runs if NO error occurs
try:
age = int(input("Enter your age: "))
except ValueError:
print("Invalid age!")
else:
# Only runs if no error occurred
print(f"You are {age} years old")
finally - Always runs (regardless of whether there is an error or not)
try:
file = open("data.txt", "r")
content = file.read()
except FileNotFoundError:
print("File not found!")
finally:
# Always runs, even if there was an error
print("Closing file operations")
file.close()
Note: When using with statements, you don’t need finally for files because they close automatically.
Complete pattern
try:
# Try to do something risky
result = risky_operation()
except SpecificError:
# Handle specific error
print("Specific error occurred")
else:
# Only if no error
print("Success!")
finally:
# Always runs
print("Cleanup complete")
Good practices
Good Practices
Catch specific errors - not all errors blindly
Specific error handling helps you understand precisely what is wrong since you can specify different behaviours/messages for different kinds of errors. You can also use it to identify errors that were not expected by forcing the program to fail or print out a special message indicating that the error was not anticipated.
❌ Bad practice - catching everything:
try:
age = int(input("Enter age: "))
score = scores[age]
except:
print("Something went wrong!")
# What went wrong? ValueError? IndexError? We don't know!
✅ Good practice - catching specific errors:
try:
age = int(input("Enter age: "))
score = scores[age]
except ValueError:
print("Please enter a valid number!")
except IndexError:
print("Age is out of range for the scores list!")
Provide helpful messages - tell users what went wrong
Error messages should be an intrinsic part of your program’s design since it can have a significant impact on its user-friendliness. Helpful error messages should help users to understand and correct what they did wrong so they can interact effectively with your program.
❌ Bad practice - catching everything:
try:
flashcards = {"Dog": "Hund", "Cat": "Katze"}
word = input("Enter a word: ")
print(flashcards[word])
except KeyError:
print("Error!") # Not helpful!
✅ Good practice - clear, actionable messages:
try:
flashcards = {"Dog": "Hund", "Cat": "Katze"}
word = input("Enter a word: ")
print(flashcards[word])
except KeyError:
print(f"'{word}' not found. Available words: {', '.join(flashcards.keys())}")
Keep try blocks small - easier to debug
By keeping the try blocks small, you can handle different errors differently and make it clear which line caused the error. This makes your code easier to debug and prevents catching unrelated errors by accident (which should have been flagged and corrected).
❌ Bad practice - too much code in try block:
try:
# Doing too many things at once
name = input("Enter name: ")
age = int(input("Enter age: "))
height = float(input("Enter height: "))
user_data = {"name": name, "age": age, "height": height}
with open("users.txt", "a") as file:
file.write(str(user_data))
scores = [85, 92, 78]
score = scores[age]
print(f"Score: {score}")
except:
print("Something failed!") # Which line caused the error?
✅ Good practice - separate try blocks for different operations:
# Get user input with validation
try:
age = int(input("Enter age: "))
except ValueError:
print("Age must be a number!")
age = 0
try:
height = float(input("Enter height: "))
except ValueError:
print("Height must be a number!")
height = 0.0
# Access list with specific error handling
try:
scores = [85, 92, 78]
score = scores[age]
print(f"Score: {score}")
except IndexError:
print(f"No score available for age {age}")
# File operations separate
try:
with open("users.txt", "a") as file:
file.write(f"{age},{height}\n")
except FileNotFoundError:
print("Could not save to file!")
Use else for success logic
Using else improves code readability by dedicating the try block to the risky operation and making it clear what happens on success, as opposed to failure.
✅ Good practice - separate error handling from success:
try:
number = int(input("Enter a number: "))
except ValueError:
print("Invalid number!")
else:
# Only runs if conversion succeeded
doubled = number * 2
print(f"{number} × 2 = {doubled}")
Don’t hide errors you can’t handle
Unexpected errors should crash the program during development so that you can investigate and fix them. If you just use a catch all try statement and let the program continue, there is a good chance that your program doesn’t behave the way you expect it to and will not fulfil its requirements (or even cause harm).
❌ Bad practice - silently ignoring errors:
try:
important_calculation()
except:
pass # Oops, we just hid a serious problem!
✅ Good practice - only catch errors you can handle:
try:
user_age = int(input("Enter age: "))
except ValueError:
print("Using default age of 18")
user_age = 18
# Let other unexpected errors crash the program so we know about them!
