🚀 Alguni Start learning

Unit 18: When Things Break

Raising and handling problems.

Unit 18 of 31 in Python for kids. Its 5 lessons are Raising the Alarm, Else and Finally, The Family of Problems, Your Own Kind of Problem and Problem Master — below is everything each one explains, and a question or two from it to try.

Every sample on this page was run through real Python before it shipped, and prints exactly what it says it prints.

This unit opens with a fortnight’s trial of everything — no card needed — or with a family plan, bought in the iPhone app. The first two units of every track are free for ever. Try it in the app.

🚨 Raising the Alarm

Stopping on purpose

Sometimes carrying on would be worse than stopping. raise reports a problem yourself, the same way Python does when something goes wrong.

Python

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

try:
    set_age(-5)
except ValueError as e:
    print("Problem:", e)

It prints

Problem: age cannot be negative

Catching the message too

as e gives you the problem itself, and printing it shows the message that was raised.

Python

try:
    raise ValueError("something is wrong")
except ValueError as e:
    print(e)

It prints

something is wrong

Try it yourself

Why raise an error instead of just printing a warning?

  • It looks more professional
  • Printing lets the program carry on with bad data; raising stops it before it does damage
  • It is faster
  • There is no difference

What does this print?

Python

def divide(a, b):
    if b == 0:
        raise ValueError("cannot divide by zero")
    return a / b

try:
    print(divide(10, 2))
    print(divide(1, 0))
except ValueError as e:
    print(e)

Answer them in the app

🧹 Else and Finally

Something that always happens

finally runs whether things went well or badly. It is where you tidy up — closing things, saying goodbye — so nothing is left half done.

Python

try:
    print(int("5"))
except ValueError:
    print("bad number")
finally:
    print("all done")

It prints

5
all done

Even when it breaks

The point of finally is that it happens either way.

Python

try:
    print(int("cat"))
except ValueError:
    print("bad number")
finally:
    print("all done")

It prints

bad number
all done

Only when nothing went wrong

else is the opposite: it runs only when the try finished with no problem at all.

Python

try:
    n = int("7")
except ValueError:
    print("bad")
else:
    print("worked, got", n)
finally:
    print("bye")

It prints

worked, got 7
bye

Try it yourself

What does this print?

Python

try:
    n = int("x")
except ValueError:
    print("caught")
else:
    print("fine")
finally:
    print("end")

When does the else part of a try run?

  • Always
  • Only when something went wrong
  • Only when nothing went wrong
  • Never

Answer them in the app

🌳 The Family of Problems

Every problem has a name

Python has a name for each kind of thing that can go wrong. Knowing them lets you catch exactly the one you expect.

Python

for thing in ["cat", None]:
    try:
        print(int(thing))
    except ValueError:
        print("ValueError - wrong sort of text")
    except TypeError:
        print("TypeError - wrong sort of thing entirely")

It prints

ValueError - wrong sort of text
TypeError - wrong sort of thing entirely

Catching several at once

Put them in brackets to handle more than one the same way.

Python

for thing in ["cat", None]:
    try:
        print(int(thing))
    except (ValueError, TypeError):
        print("cannot turn that into a number")

It prints

cannot turn that into a number
cannot turn that into a number

The common ones worth knowing

These four cover most of what a program hits: a wrong value, a wrong type, a list position that does not exist, and a dictionary key that does not exist.

Python

try:
    [1, 2, 3][10]
except IndexError:
    print("IndexError")

try:
    {"a": 1}["b"]
except KeyError:
    print("KeyError")

It prints

IndexError
KeyError

Try it yourself

Which problem does my_list[10] cause on a list of 3 things?

  • ValueError
  • IndexError
  • KeyError
  • TypeError

Why is except Exception: — catching everything — usually a bad idea?

  • It is slower
  • It hides bugs you did not expect, including typos in your own code
  • It only works once
  • Python does not allow it

Answer them in the app

🏷️ Your Own Kind of Problem

Naming a problem yourself

Make a class that inherits Exception and you have your own kind of problem, with a name that says exactly what went wrong in *your* program.

Python

class TooLoud(Exception):
    pass

try:
    raise TooLoud("turn it down!")
except TooLoud as e:
    print("caught:", e)

It prints

caught: turn it down!

It is an ordinary class

Your exception can hold information too, just like any other class — useful when the catcher needs to know more than a message.

Python

class OutOfStock(Exception):
    def __init__(self, item):
        super().__init__(f"no {item} left")
        self.item = item

try:
    raise OutOfStock("milk")
except OutOfStock as e:
    print(e)
    print(e.item)

It prints

no milk left
milk

Try it yourself

Why make your own exception instead of using ValueError?

  • It runs faster
  • The name says what went wrong, and callers can catch just that one
  • ValueError is broken
  • You have to

What does this print?

Python

class Nope(Exception):
    pass

try:
    raise Nope("not allowed")
except Exception as e:
    print(type(e).__name__)
    print(e)

Answer them in the app

🏆 Problem Master

Try it yourself

What does this print?

Python

try:
    print("a")
    raise ValueError("bad")
    print("b")
except ValueError:
    print("c")
finally:
    print("d")

Answer it in the app