Facing up to mistakes

INPUT · Slides

Raise an error yourself

01 / 07

A wrong value goes through anyway

Hand a function a value you never expected and Python says nothing in particular. It works with it and shows the result all the same.

Below, a negative number arrives as an age and sails through untouched. Fail to stop it here and you get a meaningless result far further down, with no way back to the cause.

def say_age(age):    print(f"{age} years old")say_age(-3)

Result

-3 years old

02 / 07

Raise one yourself with raise

Write a kind of error and a message after raise and you cause an error there and then. The shape is raise ValueError("age must be 0 or more").

ValueError is the kind that means "the value is wrong". The names you read in the last group are yours to use as well.

Once it reaches the raise line, nothing past it runs. With no except to catch it, the program simply stops.

def check(age):    if age < 0:        raise ValueError("age must be 0 or more")    print(f"{age} years old")check(12)check(-3)

Result

12 years old
ValueError: age must be 0 or more

03 / 07

Catch it on the calling side

An error you raise can be caught by a try and except on the calling side, exactly as in the last lesson.

Take it with as e and show it, and your own wording comes back to you. Which is why words that make sense to a reader are worth writing: the reader is usually you.

def check(age):    if age < 0:        raise ValueError("age must be 0 or more")    print(f"{age} years old")try:    check(-3)except ValueError as e:    print(e)

Result

age must be 0 or more

04 / 07

Turn it away at the door

The place for a raise is wherever you notice the value is wrong. Checking at the door of a function and turning it away there is the clearest of all.

Let it through and the odd value passes through your calculations and surfaces far later as "I have no idea why this is the result". Fail close to the cause and the cause stays close.

def buy(stock, count):    if count > stock:        raise ValueError("not enough stock")    print(f"{stock - count} left")try:    buy(5, 9)except ValueError as e:    print(e)

Result

not enough stock

05 / 07

Choosing between assert and raise

The two are alike, but they face different ways.

  • assert … a device for catching your own mistakes. If it fails, the program has a bug
  • raise … how you tell somebody the value that came in is wrong. You expect it to be caught

For the kind, pick from the ones that already exist. A wrong value is a ValueError; kinds that do not go together, a TypeError. A kind of error that is yours alone needs a class, so that pleasure waits for a later chapter.

def divide(a, b):    if b == 0:        raise ValueError("cannot divide by zero")    return a // btry:    print(divide(10, 2))    print(divide(10, 0))except ValueError as e:    print(e)

Result

5
cannot divide by zero

06 / 07

The message is for whoever reads it

A message that says only "error" tells you nothing when you come back to it. Put in what is wrong, and why that is a problem.

  • says nothing … "no good"
  • says something … "the name is empty"

Stopping is not unkindness. It is a favour to whoever meets it later.

07 / 07

Errors become part of the design

That is the whole business of getting along with errors.

  • read … narrow the cause down from the kind and the traceback
  • prepare … write your assumptions down with assert
  • divert … carry on rather than fall over, with try and except
  • speak up … say "this is not right" with raise

Once you decide for yourself where errors are raised and where they are caught, an error stops being the end of the road. Deciding where you notice and where you fix is exactly what it means to use them as part of the design.