How errors move from function to function
When an exception happens inside a function and the function doesn't handle it, the function stops right there, and the exception moves on to the code that called the function. If that code doesn't handle it either, it moves on to that code's caller, and so on. If nothing on the way handles it, the program stops, and the traceback lists every call on the way:
def average(numbers):
return sum(numbers) / len(numbers)
def report(name, numbers):
print(name, "average:", average(numbers))
report("Week 1", [3, 5, 7])
report("Week 2", [])
Week 1 average: 5.0Traceback (most recent call last):
File "main.py", line 10, in <module>
report("Week 2", [])
~~~~~~^^^^^^^^^^^^^^
File "main.py", line 6, in report
print(name, "average:", average(numbers))
~~~~~~~^^^^^^^^^
File "main.py", line 2, in average
return sum(numbers) / len(numbers)
~~~~~~~~~~~~~^~~~~~~~~~~~~~
ZeroDivisionError: division by zeroRead it from the bottom up: the error happened in average on line 2, which was called by report on line 6, which was called by the main program on line 10. The line that's really wrong is often one of the callers: here it's the main program, which asked for the average of an empty list.
Handling it where you know what to do
So an exception can be handled in a different place from where it happens, and often the right place is the caller. A function that reads a file can't know whether a missing file is a disaster or just means there's no data yet, but the code that calls it does:
def read_numbers(filename):
numbers = []
with open(filename) as file:
for line in file:
numbers.append(int(line))
return numbers
try:
savings = read_numbers("savings.txt")
except FileNotFoundError:
savings = []
print("Savings:", savings)
Savings: []There's no savings.txt, so open raises a FileNotFoundError inside read_numbers. The function stops at once (it never gets to its return), and the exception arrives at the try around the call, whose except decides what to do.
What doesn't run
When an exception leaves a try block, the rest of the block is skipped, including any later calls:
def check(value):
print("Checking", value)
number = int(value)
print("Valid:", number)
return number
try:
check("12")
check("twelve")
check("7")
except ValueError:
print("Stopped at an invalid value")
print("Done")
Checking 12
Valid: 12
Checking twelve
Stopped at an invalid value
Donecheck("twelve") stops at int, so it never prints Valid:, and check("7") is never called at all. To check every value whatever happens, the try would go inside a loop, around each call.