Python Arena
Step 5 of 5

Raising errors yourself

Your own code can raise an exception too, with the raise statement. Causing errors on purpose may sound odd, but it's the clearest way for a function to say "I can't do this with these arguments". A function that only prints an error message is easy to miss, especially when its result goes on to other code, and one that returns a special value, such as -1, risks having that value used as if it were a real result. An exception can't be missed: it stops the program unless the caller handles it.

def hours_to_minutes(hours):
    if hours < 0:
        raise ValueError(f"hours can't be negative: {hours}")
    return hours * 60


print(hours_to_minutes(2))
print(hours_to_minutes(-3))
Output
120
Python stops with
Traceback (most recent call last):
  File "main.py", line 8, in <module>
    print(hours_to_minutes(-3))
          ~~~~~~~~~~~~~~~~^^^^
  File "main.py", line 3, in hours_to_minutes
    raise ValueError(f"hours can't be negative: {hours}")
ValueError: hours can't be negative: -3

After raise comes the exception: its kind, with a message in parentheses. ValueError is the usual choice for a value that isn't acceptable, and TypeError for a value of the wrong type. The message is shown in the traceback, so make it say what was wrong.

Catching your own exceptions

The caller handles a raised exception like any other. With as, the except line also gives the exception a name, and printing it prints its message:

def booking(guests, nights):
    if guests < 1 or guests > 6:
        raise ValueError(f"a room takes 1 to 6 guests, not {guests}")
    if nights < 1:
        raise ValueError(f"a stay is at least one night, not {nights}")
    return (guests, nights)


try:
    print("Booked:", booking(2, 3))
    print("Booked:", booking(9, 1))
except ValueError as error:
    print("Can't book:", error)
Output
Booked: (2, 3)
Can't book: a room takes 1 to 6 guests, not 9

This is a good division of work. The function checks its arguments and raises an exception when they're wrong; it doesn't need to know who called it or how to talk to the user. The code that talks to the user catches the exception and decides what to say.

Your task

Challenge 1 of 3

Fill in the blanks so that set_volume(level) raises a ValueError with the message volume must be 0 to 10 when the level is outside 0 to 10, and otherwise returns the level. This code

print(set_volume(7))
try:
    set_volume(11)
except ValueError as error:
    print("Error:", error)

prints:

7
Error: volume must be 0 to 10

Challenge 2

Write a function withdraw(balance, amount) that returns the balance left after taking amount out of an account. It raises a ValueError:

  • with the message amount must be positive if the amount is 0 or less;
  • with the message not enough money if the amount is more than the balance.

For example, withdraw(100, 30) returns 70, and withdraw(100, 130) raises a ValueError whose message is not enough money.

Challenge 3

The starter code has a function parse_size(text) that turns a size like 3x4 into a tuple (3, 4), and raises a ValueError when the text isn't a valid size. Write the main program: it goes through the lines of the file sizes.txt and prints the area of each valid size, or Skipped: and the exception's message for an invalid one.

With this sizes.txt

3x4
10x2
5x
0x8
2x3x4

the program prints:

3x4: area 12
10x2: area 20
Skipped: invalid literal for int() with base 10: ''
Skipped: 0x8 has a side of zero or less
Skipped: 2x3x4 needs exactly one x