Different errors and finally
Problem
The bill for zero people
The program splits the restaurant bill evenly. You already know about wrong input, and you catch it:
try: bill = int(input()) people = int(input()) print(f"Per person: €{bill // people}")except ValueError: print("Enter whole numbers.")The bill is €90, and someone enters 0 for the number of people. The program stops again:
Traceback (most recent call last): File "/projects/bill/bill.py", line 4, in <module> print(f"Per person: €{bill // people}") ~~~~~^^~~~~~~~ZeroDivisionError: integer division or modulo by zeroexcept is there, but it does not help. It catches only ValueError, and this is a different kind of exception.
The idea
Several except blocks
One try can be followed by several except blocks, one for each kind of error:
try: bill = int(input()) people = int(input()) print(f"Per person: €{bill // people}")except ValueError: print("Enter whole numbers.")except ZeroDivisionError: print("People cannot be 0.")| Input | Output |
|---|---|
| 90 and 3 | Per person: €30 |
| 90 and 0 | People cannot be 0. |
| 90 and three | Enter whole numbers. |
The blocks are checked from top to bottom. The first one whose kind matches runs, and only that one. The rest are skipped.
try: bill = int(input()) people = int(input()) print(f"Per person: €{bill // people}")except ValueError: print("Enter whole numbers.")except ZeroDivisionError: print("People cannot be 0.")Step by step, in words
- First run: the bill is €90, and there are 0 people.
- 90 / 0 is division by zero: ZeroDivisionError is thrown.
- The first except is for ValueError. It does not match, so it is skipped.
- The second one matches. It runs, and only it.
- Second run: "three" is typed in instead of the number of people.
- int() cannot make a number: ValueError is thrown.
- The very first except matches. The second is never reached.
- The blocks are checked from top to bottom. The first one that matches runs.
More precisely, if you are curious
- When two kinds are handled the same way, you can catch them in one block with a tuple:
except (ValueError, ZeroDivisionError):.
The idea
The family of exceptions
Exceptions are classes, related by inheritance. Here are the most common ones:
BaseException├── KeyboardInterrupt Ctrl+C, not caught├── SystemExit sys.exit(), not caught└── Exception ├── ArithmeticError │ ├── ZeroDivisionError │ └── OverflowError ├── LookupError │ ├── IndexError │ └── KeyError ├── ValueError ├── TypeError └── OSError files, network └── FileNotFoundErrorexcept catches the kind you name and all of its subclasses. This is the polymorphism from the module on inheritance: an object of a subclass is also of the parent's kind. issubclass shows it:
print(issubclass(ZeroDivisionError, ArithmeticError))print(issubclass(IndexError, LookupError))print(issubclass(ValueError, Exception))print(issubclass(KeyboardInterrupt, Exception))TrueTrueTrueFalseThat is why except Exception as e: catches almost everything:
def split(bill_text, people_text): try: bill = int(bill_text) people = int(people_text) print(f"Per person: €{bill // people}") except Exception as e: print(f"{type(e).__name__}: {e}")
split("90", "3")split("90", "0")split("90", "three")Per person: €30ZeroDivisionError: integer division or modulo by zeroValueError: invalid literal for int() with base 10: 'three'type(e).__name__ gives the name of the object's real class. The except is for Exception, but the object is of a subclass.
More precisely, if you are curious
KeyboardInterrupt(Ctrl+C) andSystemExit(sys.exit()) are not subclasses ofExceptionbut ofBaseException. That wayexcept Exception:does not keep the program from being stopped.except:with no kind andexcept BaseException:catch them too. Do not write those.
- Catch as specifically as you can. except Exception: also hides errors you did not expect, such as a bug in your own code.
Try it
Which catch?
What will the program print?
seats = [4, 2, 6]try: print(f"Table 4: {seats[3]} seats")except ZeroDivisionError: print("Division by zero")except LookupError: print("Lookup error")except Exception: print("General error")seats[3] is outside the list of 3 elements: IndexError. It is not a ZeroDivisionError, so the first block is skipped. It is a subclass of LookupError, so the second one matches and runs. The third is never reached, even though it would match too.Try it
Find the bug
text = "0" the program prints "Something went wrong." and not "People cannot be 0.". Which line is the bug on?Pick the line with the bug.
Exception is the parent of ZeroDivisionError. The first except catches everything, and the second can never be reached. Python says nothing: the program runs and just prints the general message. The rule is from the specific to the general: swap the two blocks.The idea
Nobody makes you
Java has checked exceptions: the compiler does not accept the program until you catch them or declare them with throws. Python has none of those. No exception has to be caught:
import time
def print_receipt(): print("Printing the receipt...") time.sleep(0.3) print("Done.")
print_receipt()Printing the receipt...Done.import time brings in the module for time, and time.sleep(0.3) pauses the program for 0.3 seconds. If you press Ctrl+C during the pause, Python throws KeyboardInterrupt, but nobody asks you to catch it. The same goes for int() and ValueError.
So the responsibility is yours. Which exceptions a function throws is written in its documentation, usually with the word "Raises". Read it before you decide whether to catch.
The idea
finally: always
The till's printer has to be freed after every receipt, or the next one will not print. Whatever happens, success or error:
def print_receipt(qty_text): print("The printer is busy.") try: qty = int(qty_text) print(f"Coffee: {qty} x €2 = €{qty * 2}") except ValueError: print(f"Wrong quantity: {qty_text}") finally: print("The printer is free.")
print_receipt("3")print_receipt("three")The printer is busy.Coffee: 3 x €2 = €6The printer is free.The printer is busy.Wrong quantity: threeThe printer is free.finally runs always: when everything goes well, when the exception is caught, and even when none of these except blocks catches it. In that last case finally runs, and the exception keeps going up to the next try that can catch it.
Why not just a line after the try/except? Because that line is skipped if the exception is of a kind that except does not catch, or if try has a return. finally is not skipped.
Python also has else: it runs only if no exception was thrown in try. That way try holds only the line that can throw, and the printing comes after it:
def print_receipt(qty_text): print("The printer is busy.") try: qty = int(qty_text) except ValueError: print(f"Wrong quantity: {qty_text}") else: print(f"Coffee: {qty} x €2 = €{qty * 2}") finally: print("The printer is free.")
print_receipt("3")print_receipt("three")The printer is busy.Coffee: 3 x €2 = €6The printer is free.The printer is busy.Wrong quantity: threeThe printer is free.More precisely, if you are curious
- You can also have
trywithfinallyand noexcept: the exception is not caught, but the cleanup runs.
- For resources that have to be closed, such as files and database connections, there is a shorter form:
with. We will see it when we get to files. It closes the resource by itself, just asfinallywould.
finallyruns even onsys.exit(): in Python that is an exception too (SystemExit), and it goes up throughfinally.
Try it
finally and return
What will the program print?
def reserve(guests): try: if guests > 6: return f"No table for {guests}" return f"Table for {guests}" finally: print("Logged the attempt.")
print(reserve(8))A return in try does not skip finally. The function runs finally first, then returns the text. That is why "Logged the attempt." is printed before the result, which print gets only when the function is done.
Exercise
Programs with several except blocks
Solve these in your editor. Do not print prompts.
1. Price per person. The prices are in the list [120, 75, 300]. Read a product number (from 1) and the number of people, and print the price per person with integer division. Catch three things: a product that does not exist, zero people, and everything else (then print the exception's message).
| Input | Output |
|---|---|
| 2 and 3 | Per person: €25 |
| 5 and 2 | No such product. |
| 1 and 0 | The number of people cannot be 0. |
| 1 and two | Error: invalid literal for int() with base 10: 'two' |
2. Calculator. Read a whole number, an operator (+, -, * or /) and a second whole number, each on its own line. Print the calculation (division is //). On text instead of a number say "Enter whole numbers.", on division by zero "Division by zero.", and on any other operator "Unknown operator: " followed by the operator. A match is handy for the operator.
84/484 / 4 = 2184/0Division by zero.More tasks
- The card terminal. The function
pay(amount_text)prints "The terminal is connected.", then "Paid €X" or a message about wrong input, and infinally"The terminal is off." Call it with "25" and with "twenty". - Average grade without crashes. Read 5 lines with grades. Skip the ones that are not whole numbers (an exception) or are not from 2 to 6 (an
if). Print the average with integer division. If no grade is valid, the division by zero throws: catch it and print "No valid grades." - Predict and check. Write a
trywithexceptandfinally, where each block prints a line. Predict the output on success, on a caught exception and on an uncaught one, then run it.
Test