Skip to content

ይህ ትምህርት ገና ወደ አማርኛ አልተተረጎመም፤ ስለዚህ በእንግሊዝኛ ቀርቧል። የእንግሊዝኛውን ገጽ ክፈቱ

Raising your own errors

4 min read

So far, errors have come from Python: int() raises when it can't convert. Your own functions can do the same. When a function is given something it can't work with, the most useful thing it can do is stop and say so clearly. By the end of this lesson your functions will raise errors with messages that tell the caller exactly what's wrong.

A quiet wrong answer

Here's grade_for from lesson 5.8. A score must be 0 to 100. Predict what it does with 150:

ውጤቱን ገምቱ

Decide before you look. Guessing wrong is how this sticks.

def grade_for(score):
    if score >= 85:
        return "A"
    elif score >= 70:
        return "B"
    elif score >= 50:
        return "C"
    else:
        return "F"

print(grade_for(150))
Pick the output

Python prints

A

Nothing in the function checks the range. 150 >= 85, so it returns A, quietly.

Python only knows what you tell it; it has no idea a score stops at 100. A typo like 150 for 50 gets an A, the A goes on a report, and nobody notices. A wrong answer that looks right is worse than an error, because an error at least stops the program where the problem is.

raise

To an error, write raise, then the error type with a message in parentheses. It stops the function on the spot, like return, but sends an error to the caller instead of a value. This shorter grade function uses pass and fail:

def grade_for(score):
    if score < 0 or score > 100:
        raise ValueError("score must be 0-100")
    if score >= 50:
        return "pass"
    return "fail"

print(grade_for(150))

In the editor, press Escape then Tab to move on.

The traceback ends with ValueError: score must be 0-100, and the line above it is the raise line. ValueError is the usual type for "right type, wrong value". In lesson 5.8, grade_for only calculated and main() decided what to print; errors split the work the same way. The function raises; the caller decides what to do about it. except ValueError as e gives the error a name, e, and printing e shows its message:

def grade_for(score):
    if score < 0 or score > 100:
        raise ValueError("score must be 0-100")
    if score >= 50:
        return "pass"
    return "fail"

for score in [72, 150]:
    try:
        print(score, grade_for(score))
    except ValueError as e:
        print("Can't grade", score, "-", e)

In the editor, press Escape then Tab to move on.

Add -5 to the list and run it: it's caught the same way.

The message must be inside the error, not the error itself:

finally (lesson 7.2) runs even when the function is leaving with return. Predict:

ውጤቱን ገምቱ

Decide before you look. Guessing wrong is how this sticks.

def check():
    try:
        return "try"
    finally:
        print("finally runs")

print(check())
Pick the output

Python prints

finally runs
try

return 'try' starts to leave the function, but finally runs first and prints. Then the call hands back 'try', and print shows it.

If you picked try alone, you pictured return skipping finally. It never does.

Your own kind of error

A payment app needs an error that means exactly "not enough money". You can make one with two short lines:

class NotEnoughBalance(Exception):
    pass

def pay(balance, amount):
    if amount > balance:
        raise NotEnoughBalance(f"balance is {balance} birr")
    return balance - amount

try:
    balance = pay(50, 120)
except NotEnoughBalance as e:
    print("Payment failed:", e)

In the editor, press Escape then Tab to move on.

Read the first two lines as a recipe: "a new kind of error, called NotEnoughBalance, based on Exception". That's a . class makes a new type; pass (lesson 2.6) says it needs nothing added. How classes work is a whole module later (Module 10), so for now this is a recipe to copy: change the name, keep the rest. It behaves like any error: you raise it with a message, and except NotEnoughBalance catches this problem and no other.

Error types come in families. ZeroDivisionError is a kind of ArithmeticError, which is a kind of Exception, and except catches the type it names and every kind of it:

try:
    print(10 / 0)
except ArithmeticError:
    print("A maths problem")

In the editor, press Escape then Tab to move on.

NotEnoughBalance is a kind of Exception too; that's what the (Exception) says.

When should a function raise, and when should it return None, like to_int in lesson 7.2? Raise when the call is wrong: a correct program never asks for it, like a score of 150. Return None when "no answer" is a normal result the caller expects, like text that isn't a number or a name that isn't in the list.

ጥያቄ

pay(500, -20) asks to pay a negative amount. What should pay do?

መልመጃ

withdraw

Write withdraw(balance, amount). Raise ValueError("amount can't be negative") for a negative amount, and NotEnoughBalance with the balance in its message when the amount is more than the balance. Otherwise return the new balance. Each test starts from 500.

Output
100: new balance 400
-20: amount can't be negative
900: balance is 500 birr

Your code

class NotEnoughBalance(Exception):
    pass

def withdraw(balance, amount):
    # Raise for a negative amount or not enough balance; else return the new balance
    pass

for amount in [100, -20, 900]:
    try:
        print(f"{amount}: new balance {withdraw(500, amount)}")
    except ValueError as e:
        print(f"{amount}: {e}")
    except NotEnoughBalance as e:
        print(f"{amount}: {e}")

In the editor, press Escape then Tab to move on.

መፍትሄውን አሳይ

This is one way to solve it, not the only one. If yours prints the same thing, it works.

class NotEnoughBalance(Exception):
    pass

def withdraw(balance, amount):
    if amount < 0:
        raise ValueError("amount can't be negative")
    if amount > balance:
        raise NotEnoughBalance(f"balance is {balance} birr")
    return balance - amount

for amount in [100, -20, 900]:
    try:
        print(f"{amount}: new balance {withdraw(500, amount)}")
    except ValueError as e:
        print(f"{amount}: {e}")
    except NotEnoughBalance as e:
        print(f"{amount}: {e}")

ዋና ዋና ነጥቦች

  • A function given bad input should raise an error, not return a wrong answer quietly.
  • raise ValueError("message") stops the function and sends the error to the caller; except ValueError as e gets the message.
  • finally runs even when a function returns.
  • class NotEnoughBalance(Exception): pass makes your own error type; except also catches every kind of the type it names.
  • Raise for calls that are wrong; return None for "no answer" the caller expects.