When something goes wrong
Problem
12a instead of 12
The concert box office asks how many tickets you want. A ticket is €15.
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:
How many tickets? 12aTraceback (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:
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 rangeRead the traceback. Which line of the program is the bug on?
Pick the line with the bug.
last_score, line 2. The line above it is line 6: the function was called from there, but the bug is not there. The last line also says what it is: an index outside the list. With 5 elements the last index is len(scores) - 1. In Python you can write it shorter: scores[-1] is the last element.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!")12aHow many tickets? That is not a whole number.Thank you!- After
exceptyou write which kind of exception you catch, hereValueError. - If everything in
trygoes well,exceptis skipped. With the input12the output is "Total: €180" and "Thank you!". - After
exceptthe 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
- The program asks how many tickets. You type "12a".
- In try: int() tries to make a number out of "12a".
- It cannot. The function throws ValueError.
- Execution jumps straight into except. The "Total" line is skipped.
- except is for exactly this kind of error: it prints the message for the customer.
- After except the program continues. It does not stop.
More precisely, if you are curious
- In Python a variable created in
tryexists after it too: blocks do not have their own scope. But ifint()has thrown,ticketsis never created, andprint(tickets)after the block stops withNameError.
try: tickets = int("12")except ValueError: print("That is not a whole number.")print(tickets)12- The name of the exception is optional:
except ValueError:. If you need it, you writeexcept 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")"Reading" is printed because it comes before the error. Then int() throws, and execution jumps straight into except. "Got" is skipped. After except the program continues with "End".
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}")12aError: 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}")12atwelve12How many tickets? Enter a whole number, for example 3.How many tickets? Enter a whole number, for example 3.How many tickets? Total: €180while 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. CatchingValueErroris the usual way: "try it and catch it" is a habit in Python. There istext.isdigit(), but it saysFalsefor"-3"too, so it does not replaceint():
print("12".isdigit())print("12a".isdigit())print("-3".isdigit())TrueFalseFalseTry 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:
ZeroDivisionError: integer division or modulo by zeroWhat about ordinary division /, which gives a decimal number?
What will the program print?
revenue = 90orders = 0average = revenue / ordersprint(average)ZeroDivisionError: with //, with /, and with decimal numbers. The program stops instead of going on with a wrong value. If there may be no orders, check the divisor with if or catch ZeroDivisionError.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"))infnanExercise
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.
123.45123.abcThe number is: 123.45Error: 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.
7There 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.
900No 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,3the output isSum: 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