Skip to content
wannacode

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:

bill.py
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:

Error
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 zero

except 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
  1. First run: the bill is €90, and there are 0 people.
  2. 90 / 0 is division by zero: ZeroDivisionError is thrown.
  3. The first except is for ValueError. It does not match, so it is skipped.
  4. The second one matches. It runs, and only it.
  5. Second run: "three" is typed in instead of the number of people.
  6. int() cannot make a number: ValueError is thrown.
  7. The very first except matches. The second is never reached.
  8. 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:

The hierarchy
BaseException
├── KeyboardInterrupt Ctrl+C, not caught
├── SystemExit sys.exit(), not caught
└── Exception
├── ArithmeticError
│ ├── ZeroDivisionError
│ └── OverflowError
├── LookupError
│ ├── IndexError
│ └── KeyError
├── ValueError
├── TypeError
└── OSError files, network
└── FileNotFoundError

except 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))
Output
True
True
True
False

That 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")
Output
Per person: €30
ZeroDivisionError: integer division or modulo by zero
ValueError: 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) and SystemExit (sys.exit()) are not subclasses of Exception but of BaseException. That way except Exception: does not keep the program from being stopped. except: with no kind and except 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")
Answer

Try it

Find the bug

With 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.

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()
Output
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")
Output
The printer is busy.
Coffee: 3 x €2 = €6
The printer is free.
The printer is busy.
Wrong quantity: three
The 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")
Output
The printer is busy.
Coffee: 3 x €2 = €6
The printer is free.
The printer is busy.
Wrong quantity: three
The printer is free.
More precisely, if you are curious
  • You can also have try with finally and no except: 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 as finally would.
  • finally runs even on sys.exit(): in Python that is an exception too (SystemExit), and it goes up through finally.

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))
Answer

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.

Input
84
/
4
Output
84 / 4 = 21
Input
84
/
0
Output
Division 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 in finally "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 try with except and finally, where each block prints a line. Predict the output on success, on a caught exception and on an uncaught one, then run it.

Test

Check yourself

1. The exception matches two of the three except blocks. Which one runs?
2. Does except ArithmeticError: catch an exception of type ZeroDivisionError?
3. Why does except Exception: have to come last?
4. When does finally run?
5. When does else run after try/except?
6. Does Python make you catch ValueError around int()?

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