Errors and Exceptions
Every program meets problems: a user types letters where a number was expected, a file is missing, a value is zero when you divide by it. When Python hits a problem it can't handle, it raises an exception and stops. In this lesson you'll learn to catch exceptions, so your program can respond sensibly instead of crashing.
Reading an exception
You've seen plenty of these already. Run this:
numbers = [10, 20, 30] print(numbers[5])
The last line tells you the type of exception (IndexError) and a message explaining it. The lines above show where it happened.
Some exceptions you'll meet often:
| Exception | Raised when… | Example |
|---|---|---|
ValueError |
a value has the right type but a bad value | int("abc") |
TypeError |
an operation gets the wrong type | "5" + 5 |
NameError |
a name isn't defined | prnt("hi") |
IndexError |
a list index is out of range | [1, 2][5] |
KeyError |
a dictionary key doesn't exist | {"a": 1}["b"] |
ZeroDivisionError |
you divide by zero | 10 / 0 |
FileNotFoundError |
a file doesn't exist | open("missing.txt") |
try and except
Put code that might fail in a try block. If an exception happens, Python jumps to the matching except block instead of crashing:
try:
number = int("forty-two")
print("Converted:", number)
except ValueError:
print("That's not a valid number.")
print("The program keeps going.")The line after the failing one (print("Converted:"...)) is skipped. Execution continues in the except block, then after the whole try statement.
Getting the error message
Use as to get the exception object. Printing it shows the message:
try:
result = 10 / 0
except ZeroDivisionError as e:
print("Something went wrong:", e)Handling different exceptions
Use several except blocks to handle each kind of problem differently:
def safe_divide(a, b):
try:
return a / b
except ZeroDivisionError:
return "Can't divide by zero"
except TypeError:
return "Both values must be numbers"
print(safe_divide(10, 2))
print(safe_divide(10, 0))
print(safe_divide(10, "two"))To handle several types the same way, group them in a tuple:
values = ["12", "abc", None]
for v in values:
try:
print(int(v) * 2)
except (ValueError, TypeError) as e:
print(f"Skipping {v!r}: {e}")Be specific. A bare
except:orexcept Exception:catches everything, including typos and bugs you'd want to know about. Catch only the exceptions you expect and know how to handle.
else and finally
A try statement can have two more optional parts:
elseruns only if no exception happened.finallyruns always, whether there was an exception or not. It's used for clean-up.
def parse_age(text):
try:
age = int(text)
except ValueError:
print(f" {text!r} is not a number")
else:
print(f" Age is {age}")
finally:
print(" (finished checking)")
parse_age("36")
parse_age("thirty")Validating user input
Combining a while loop with try gives you a robust way to ask for a number. Run it and try typing some letters first:
while True:
try:
age = int(input("How old are you? "))
break
except ValueError:
print("Please type a whole number.")
print(f"Next year you'll be {age + 1}.")Raising exceptions
You can raise exceptions yourself with raise. It's the right way for a function to say "I can't work with this input":
def set_age(age):
if age < 0:
raise ValueError("Age can't be negative")
return age
print(set_age(30))
print(set_age(-5))Whoever calls the function can then decide how to handle it:
def set_age(age):
if age < 0:
raise ValueError("Age can't be negative")
return age
try:
set_age(-5)
except ValueError as e:
print("Invalid input:", e)Going deeper: assert: checks for programmers optional
assert condition, "message" raises an AssertionError if the condition is false. It's a quick way to write down something that must be true at that point in your code, and to catch your own mistakes early:
def average(numbers):
assert len(numbers) > 0, "average() needs at least one number"
return sum(numbers) / len(numbers)
print(average([4, 8]))
print(average([]))Use assert for things that should never happen if your code is correct. Don't use it to check user input: Python can be run with asserts switched off (python -O), and then the check silently disappears. For input, use if and raise as shown above.
Your own exception types
For bigger programs you can define your own exception types. It takes two lines (you'll learn what class means in Classes and Objects):
class InsufficientFundsError(Exception):
pass
def withdraw(balance, amount):
if amount > balance:
raise InsufficientFundsError(f"Balance {balance} is less than {amount}")
return balance - amount
try:
withdraw(50, 80)
except InsufficientFundsError as e:
print("Declined:", e)Exercises
Exercise 1: Safe conversion
Write to_int(text) that returns the text converted to an int, or None if it can't be converted.
def to_int(text):
pass
print(to_int("42"))
print(to_int("hello"))Put the conversion inside try: and return int(text).
Catch ValueError and return None there.
Exercise 2: Safe dictionary lookup
Look up each product's price and print it like apple: 0.5. If a product is missing, catch the KeyError and print <name>: not found.
prices = {"apple": 0.5, "bread": 2.2}
shopping = ["apple", "milk", "bread"]
Inside the loop, put the lookup prices[item] in a try block.
In except KeyError:, print the not-found message instead.
Exercise 3: Sum the valid numbers
Add up only the entries that can be converted to numbers, skipping the rest. It should print Total: 37.5.
entries = ["10", "abc", "7.5", "", "20"]
Loop over the entries and add float(entry) to the total inside a try block.
Catch ValueError and use pass to skip the entries that aren't numbers.
Exercise 4: Raise an error
Write percentage(part, whole) that returns part / whole * 100. If whole is 0, raise a ValueError with the message whole must not be zero. Then call percentage(5, 0) inside try/except and print Error: followed by the message.
def percentage(part, whole):
pass
print(percentage(25, 200))Check if whole == 0: and raise ValueError("whole must not be zero").
Wrap percentage(5, 0) in try, and in except ValueError as e: print "Error:" and e.
Exception handling at a glance
A quick reference for later.
| Code | What it does |
|---|---|
try: |
Runs code that might raise an exception |
except ValueError: |
Handles one type of exception |
except ValueError as e: |
Handles it, and stores the exception in e so you can print its message |
except (ValueError, TypeError): |
Handles any of several types with the same code |
except Exception: |
Handles almost any error; use sparingly, it can hide bugs |
else: |
Runs only if the try block raised no exception |
finally: |
Always runs, error or not; good for clean-up |
raise ValueError("message") |
Raises an exception yourself |
raise |
On its own inside except: re-raises the exception you're handling |
class MyError(Exception): pass |
Defines your own exception type |
assert condition, "message" |
Raises AssertionError if the condition is false; for catching your own mistakes |
Summary
- An exception stops the program unless it is caught. The last line of the traceback names it.
try:runs risky code, andexcept SomeError:handles that error.except SomeError as egives you the error message.elseruns if nothing went wrong, andfinallyalways runs.- Catch specific exceptions, not everything.
raise ValueError("message")signals a problem from your own code.