Raising your own errors

Introduction

Catching errors is half the story. Your own functions should also raise errors when they are given something they cannot handle, so the problem is reported clearly at the place it starts.

A function that quietly returns a wrong value causes confusing errors far away from the real problem. Raising an error at once puts the report right where the mistake happened, and lets the caller decide what to do about it.

How it works

raise

def set_age(age):
    if age < 0:
        raise ValueError("age cannot be negative")
    return age

print(set_age(20))
print(set_age(-3))
Open in compiler

set_age(20) works. set_age(-3) reaches raise ValueError(...), and the program stops with that message, pointing at line 3. Raising early is better than returning a wrong value that causes a confusing error much later.

finally

try:
    result = 10 / 0
except ZeroDivisionError:
    print("Cannot divide")
finally:
    print("Done either way")
Open in compiler

A finally block runs whether or not an error happened. It is the place for clean-up, such as closing a file or a connection.

Syntax

class TooLow(Exception):
    pass

def check(x):
    if x < 0:
        raise TooLow("x must not be negative")
    return x

Example

class InsufficientFunds(Exception):
    pass

def withdraw(balance, amount):
    if amount > balance:
        raise InsufficientFunds(f"need {amount - balance} more")
    return balance - amount

try:
    withdraw(100, 250)
except InsufficientFunds as error:
    print("Refused:", error)
Open in compiler

class InsufficientFunds(Exception): pass creates a new kind of error. The caller can catch exactly that kind with except InsufficientFunds as error, and error holds the message you passed in: need 150 more.

Line by line

  1. class InsufficientFunds(Exception): pass makes a new kind of error.
  2. withdraw raises it when the amount is larger than the balance.
  3. The message says how much more is needed.
  4. The call on the try line asks for 250 from a balance of 100, so the error is raised.
  5. except InsufficientFunds as error catches it, and error holds the message.

Expected result

Refused: need 150 more

In the real world

Libraries raise specific errors so that callers can respond correctly, such as asking a user to log in again after an expired session. Web frameworks turn such errors into the right response for the browser.

Common mistakes

  • Raising a vague error: prefer a specific type (ValueError, TypeError) and a message that says what was wrong.
  • Writing raise ValueError and catching Exception everywhere, which hides real bugs.
  • Using exceptions for normal control flow, such as ending a loop; use if and break.

Best practices

  • Use the most specific error type that fits.
  • Say what was wrong in the message, and what was expected.
  • Make a custom exception when callers need to tell it apart.

Summary

raise reports a problem at the place it is found. A class that inherits from Exception creates your own kind of error, and finally runs whether or not an error happened.

Practice

Write parse_mark(text) that converts text to an int and raises ValueError("mark must be 0 to 100") when the number is out of range. Call it inside try with good and bad inputs.

Quick check

1. What happens after raise if nothing catches the error?
2. When does a finally block run?
3. What does class MyError(Exception): pass create?

Key takeaways

  • Raise as soon as you know something is wrong.
  • A clear message is half of the fix.
  • finally is the place for clean-up.

Practice problems for this lesson

My notes

Write down what you want to remember from this lesson. Notes are saved on this device as you type, and appear on your dashboard.