Learn Python / Errors and Exceptions

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])
Output

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.")
Output

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)
Output

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"))
Output

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}")
Output

Be specific. A bare except: or except 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:

  • else runs only if no exception happened.
  • finally runs 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")
Output

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}.")
Output

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))
Output

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)
Output
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([]))
Output

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)
Output

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"))
Output

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"]
Output

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"]
Output

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))
Output

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, and except SomeError: handles that error.
  • except SomeError as e gives you the error message.
  • else runs if nothing went wrong, and finally always runs.
  • Catch specific exceptions, not everything.
  • raise ValueError("message") signals a problem from your own code.