Skip to content
wannacode

When something goes wrong

Problem

12a instead of 12

The concert box office asks how many tickets you want. A ticket is €15.

tickets.py
tickets = int(input("How many tickets? "))
print(f"Total: €{tickets * 15}")

With the input 12 the program says "Total: €180". But the customer is in a hurry and hits one letter too many:

The console
How many tickets? 12a
Traceback (most recent call last):
File "/projects/tickets/tickets.py", line 1, in <module>
tickets = int(input("How many tickets? "))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: invalid literal for int() with base 10: '12a'

The program stops. Instead of the total you get a long text, and the "Total" line is never reached. What does this text mean, and how do you keep the program from stopping because of one letter?

The idea

Exception

int() has to return a number. It cannot make a number out of "12a", and returning just any number would be a lie. So the function creates an object that describes the problem and throws it. This object is called an exception.

In Python the word is raise, but the meaning is the same. If nobody catches it, the program stops and shows what happened. This text is called a traceback, and you read it from the bottom up, because the newest is at the bottom (most recent call last):

  • The last line: the kind of exception and the message. ValueError: invalid literal for int() with base 10: '12a': a wrong value, and here is the text.
  • The "File" lines above it: where it happened. This is the call stack from the recursion lesson, but from the outside in: at the top is the outermost place, and at the bottom is the function that threw. <module> means the top level code of the file.
  • Your line: the last "File" line that points to your file. Here: tickets.py, line 1. Under it is the line itself, and the ^^^ marks show the part that threw.

The messages are in English because the language writes them, not you.

Try it

Find the bug

The program should print the last score from the test. It stops with this:

Error
Traceback (most recent call last):
File "/projects/tickets/scores.py", line 6, in <module>
print(last_score(scores))
^^^^^^^^^^^^^^^^^^
File "/projects/tickets/scores.py", line 2, in last_score
return scores[len(scores)]
~~~~~~^^^^^^^^^^^^^
IndexError: list index out of range

Read the traceback. Which line of the program is the bug on?

Pick the line with the bug.

The idea

try and except

An exception can be caught. The code that might throw goes in try. What should happen on an error, you write in except (as in "except if this happens"):

text = input("How many tickets? ")
try:
tickets = int(text)
print(f"Total: €{tickets * 15}")
except ValueError:
print("That is not a whole number.")
print("Thank you!")
Input
12a
Output
How many tickets? That is not a whole number.
Thank you!
  • After except you write which kind of exception you catch, here ValueError.
  • If everything in try goes well, except is skipped. With the input 12 the output is "Total: €180" and "Thank you!".
  • After except the program continues on. It no longer stops.

text = input("How many tickets? ")
try:
tickets = int(text)
print(f"Total: €{tickets * 15}")
except ValueError:
print("That is not a whole number.")
print("Thank you!")
Step by step, in words
  1. The program asks how many tickets. You type "12a".
  2. In try: int() tries to make a number out of "12a".
  3. It cannot. The function throws ValueError.
  4. Execution jumps straight into except. The "Total" line is skipped.
  5. except is for exactly this kind of error: it prints the message for the customer.
  6. After except the program continues. It does not stop.
More precisely, if you are curious
  • In Python a variable created in try exists after it too: blocks do not have their own scope. But if int() has thrown, tickets is never created, and print(tickets) after the block stops with NameError.
try:
tickets = int("12")
except ValueError:
print("That is not a whole number.")
print(tickets)
Output
12
  • The name of the exception is optional: except ValueError:. If you need it, you write except ValueError as e:.

Try it

Which lines run?

What will the program print?

print("Start")
try:
print("Reading")
seats = int("ten")
print(f"Got {seats}")
except ValueError:
print("Bad input")
print("End")
Answer

The idea

What the exception says

The exception is an object, and e in except ValueError as e: is the variable that points to it. The message is the text of the object itself: str(e), or just {e} in an f-string.

try:
tickets = int(input())
print(f"Total: €{tickets * 15}")
except ValueError as e:
print(f"Error: {e}")
Input
12a
Output
Error: invalid literal for int() with base 10: '12a'

The message is useful to you when you are looking for the bug. To the customer you usually show your own text, in their language, such as "That is not a whole number."

The idea

Ask until it is right

The customer made a mistake. You do not have to stop. Ask again:

while True:
try:
tickets = int(input("How many tickets? "))
break
except ValueError:
print("Enter a whole number, for example 3.")
print(f"Total: €{tickets * 15}")
Input
12a
twelve
12
Output
How many tickets? Enter a whole number, for example 3.
How many tickets? Enter a whole number, for example 3.
How many tickets? Total: €180

while True keeps looping until break stops it. And break is reached only if int() succeeds. On an error, execution jumps into except, and after it the loop starts over and asks again.

More precisely, if you are curious
  • Python has no TryParse. Catching ValueError is the usual way: "try it and catch it" is a habit in Python. There is text.isdigit(), but it says False for "-3" too, so it does not replace int():
print("12".isdigit())
print("12a".isdigit())
print("-3".isdigit())
Output
True
False
False

Try it

Division by zero

Not every exception comes from the input. The average order value is the revenue divided by the number of orders. But on the first day there are no orders. With integer division, revenue // orders stops the program:

Error
ZeroDivisionError: integer division or modulo by zero

What about ordinary division /, which gives a decimal number?

What will the program print?

revenue = 90
orders = 0
average = revenue / orders
print(average)
Answer
More precisely, if you are curious
  • Python's decimal numbers follow the IEEE 754 standard and have infinity and nan, but dividing by zero does not give them: Python chooses to throw. You get infinity only if you ask for it:
print(float("inf"))
print(float("inf") - float("inf"))
Output
inf
nan

Exercise

Programs that do not stop

Solve these in your editor. Do not print prompts, so your output matches the sample.

1. Text to number. Write a function to_number(text) that returns the text as a float. Read two lines. For each, print the number or a message that it is not a number. Catch the exception where you call the function.

Input
123.45
123.abc
Output
The number is: 123.45
Error: 123.abc is not a number.

2. No such product. The prices are in the list [120, 75, 300, 45, 89]. Read a product number from 1 to 5 and print its price. If the number is outside the list, catch the exception and say so. With the input 2 the output is Price: €75.

Input
7
Output
There is no product number 7.

Watch out for the number 0: in Python prices[-1] does not throw. It is the last price, €89. A negative index counts from the end. So for 0, add an if too.

3. Average order. Read the revenue and the number of orders (whole numbers) and print the average with integer division. If there are no orders, catch the exception. With the input 900 and 4 the output is Average: €225.

Input
90
0
Output
No orders yet.

More tasks

  • Age, until it is right. Ask for an age until the input is a whole number from 0 to 120. For text like "twenty" say "That is not a number.", and for 150 say "The age must be from 0 to 120." One is an exception, the other is an if.
  • Sum of the valid ones. Read 5 lines. Add up the ones that are whole numbers and count the skipped ones: with the input 10, abc, 5, 7.5, 3 the output is Sum: 18, skipped: 2.
  • Division on demand. Read two whole numbers and print the quotient. On text instead of a number and on division by zero, the program does not stop. It asks again for both numbers.

Test

Check yourself

1. What is an exception?
2. The program stopped with a Traceback. Where do you start reading?
3. A try has four lines. The second one throws an exception, and except catches it. What happens to the third and the fourth?
4. What happens with average = revenue / orders when orders is 0?
5. Does except ValueError: catch the error from scores[10] in a list of 5 elements?

Made by Ivo Gergov as part of Evo Tech's campaign for accessible programming education ·