Topic 03

Raising Your Own

Hands-on

Your own functions have rules too. A tip percentage cannot be negative. An amount cannot be empty. When a function receives something that breaks its rules, the worst response is to carry on and produce a wrong answer three steps later. raise lets a function refuse the input immediately, with a message that says what was wrong.

Think of a pharmacist handed a prescription with a dose that makes no sense. A clear "no, and here is why" at the counter is far better than the wrong medicine and a puzzled doctor next week.

raise

raise creates an exception and sends it on its way, exactly as Python does when one of its own instructions fails. You choose the type, usually ValueError for a bad value, and write the message in brackets. The function below refuses a percentage outside zero to a hundred.

tip_check.py
def calculate_tip(bill, percent):
    """Return the tip for a bill at the given percentage."""
    if not 0 <= percent <= 100:
        raise ValueError(f"percent must be between 0 and 100, got {percent}")
    return bill * percent / 100


print(calculate_tip(40, 15))
print(calculate_tip(40, -5))
Output
6.0
Traceback (most recent call last):
  File "/Users/you/python-course/tip_check.py", line 9, in <module>
    print(calculate_tip(40, -5))
          ~~~~~~~~~~~~~^^^^^^^^
  File "/Users/you/python-course/tip_check.py", line 4, in calculate_tip
    raise ValueError(f"percent must be between 0 and 100, got {percent}")
ValueError: percent must be between 0 and 100, got -5

A Good Message

The last line of that traceback is your own sentence, and it is worth writing well, because whoever reads it has only that sentence to go on. A good message says what was wrong and what was expected, and it includes the bad value: "percent must be between 0 and 100, got -5". Compare "invalid input", which tells the reader nothing they can act on.

Check at the Edge

You do not need checks in every function. The useful place is the edge: where data enters the program from outside, such as a person typing, a file being read, or a request arriving. Check it there, once, and the inside of the program can trust what it receives. Functions deep inside can then stay short and simple.

Raising and Catching Together

A function that raises and a caller that catches work as a pair. The function states its rule; the caller decides what to do when the rule is broken. Below, main tries each percentage, and when calculate_tip refuses one, main catches the ValueError and prints the function's own message.

tip_pair.py
def calculate_tip(bill, percent):
    """Return the tip for a bill at the given percentage."""
    if not 0 <= percent <= 100:
        raise ValueError(f"percent must be between 0 and 100, got {percent}")
    return bill * percent / 100


def main():
    for percent in [15, 250]:
        try:
            print(f"Tip: {calculate_tip(40, percent)}")
        except ValueError as error:
            print(f"Could not work out the tip: {error}")


main()
Output
Tip: 6.0
Could not work out the tip: percent must be between 0 and 100, got 250

The words as error give the caught exception a name, and printing it prints its message. That is how the sentence written in calculate_tip reaches the person running main.

Where a bad value is caught
Without a check
A negative tip is calculated, added to the bill and printed, and nobody notices until later.
With raise at the edge
percent must be between 0 and 100, got -5, at the moment it arrives.
Common Confusions
  • "Returning None is gentler than raising." It pushes the problem to wherever None breaks later, with a much less helpful message.
  • "Error messages are only for programmers." Write them for whoever will read them, which is often the person using the program.
  • "Every function should check every value." Check at the edges where data enters; trust it inside.
Why It Matters
  • Failing early with a clear message saves hours of hunting for where a bad value came from.
  • A function that states its rules can be used safely by other code, and by you next month.

Knowledge Check

What does raise ValueError("...") do inside a function?

  • Stops the function and sends a ValueError to its caller
  • Prints the message and then carries on with the function
  • Returns the message as the function's result
  • Asks the person at the keyboard to correct the bad value

Which is the most useful error message?

  • invalid input, please check the value
  • something went wrong in calculate_tip, please try again
  • ValueError raised while calculating the tip
  • percent must be between 0 and 100, got -5

Where does this page recommend checking data?

  • In every single function, as its very first line
  • Where it enters the program: input, files, requests
  • Only at the very end of the program, just before printing results
  • Nowhere: Python checks every value for you automatically

In except ValueError as error:, what is error?

  • The line number where the ValueError happened
  • A true or false flag saying an error occurred
  • A new ValueError created by the except block
  • The caught exception; printing it shows its message

You got correct