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