Exceptions B: else and finally — the Full Structure
- Explain when each of the four blocks runs — try, except, else, finally
- Use
elsefor code that runs only if there was no error - Use
finallyfor cleanup actions that must always happen - Predict the order of the prints in a program with a full try
What will we learn?
In the previous lesson we used try and except. The structure has two more parts:
else— runs only if there was no error.finally— runs always, with or without an error.
Why do we need this?
Imagine code that opens a connection to a Database (the backend and databases topics), performs an action and closes the connection. What happens if the action fails in the middle? If the closing is written after the action, it will not be executed, and the connection stays open. After a hundred such failures, the server will crash because it ran out of connections.
finally guarantees that the cleanup happens in every case. else keeps the try small, and makes it clear what runs only after success.
Explanation from the basics
The four blocks
| Block | When it runs | What it is for |
|---|---|---|
try | You always start with it | The code that may fail |
except | Only if an error of the matching type was thrown | Handling the error |
else | Only if no error at all was thrown in the try | Continuing that depends on success |
finally | Always, at the end, in every case | Cleanup: closing files, connections, a finish message |
try = you try to bake a cake. except = if it burned, you order a cake from the bakery. else = if it came out well, you decorate it (you don’t decorate a burned cake!). finally = in any case, you turn off the oven and wash the dishes.
Why else and not simply continue in the try?
You can put the follow-up code inside the try. But then, if that code fails with the same error, the except will catch it by mistake, and handle the wrong problem. else makes it clear: only the risky line inside the try, and the rest outside.
finally runs even when…
- There was an error and it was caught.
- There was an error and it was not caught.
finallyruns, and then the program crashes. - There is a
returnin the middle of thetry.finallyruns before the function really returns.
Syntax
try:
# risky code
except SomeError:
# runs only if SomeError happened
else:
# runs only if NO error happened in try
finally:
# runs ALWAYS, lastRequired order: try → except (one or more) → else (optional) → finally (optional). You can also have try + finally without except.
First code example
week-2/full_try.py:
def convert(text):
print("--- converting:", text)
try:
number = int(text)
except ValueError:
print("except: not a number")
else:
print("else: success, doubled =", number * 2)
finally:
print("finally: done with", text)
convert("21")
convert("hi")Output:
--- converting: 21
else: success, doubled = 42
finally: done with 21
--- converting: hi
except: not a number
finally: done with hiBreaking down the code line by line
| Block | convert("21") | convert("hi") |
|---|---|---|
try: number = int(text) | Succeeds: number = 21 | ValueError |
except ValueError: | Skipped | Runs |
else: | Runs: prints 42 | Skipped |
finally: | Runs | Runs |
else and except never both run: either there was an error, or there was not. finally, on the other hand, runs in both cases.
Another example
A “connection” that must be closed. Here we simulate a connection to an external resource (like a Database), and see that finally closes it both when something fails in the middle and when there is a return. week-2/connection_demo.py:
def open_connection():
print("[connection opened]")
def close_connection():
print("[connection closed]")
def fetch_price(product_name, prices):
open_connection()
try:
price = prices[product_name]
except KeyError:
print("No such product:", product_name)
return None
else:
print("Found", product_name)
return price
finally:
close_connection()
catalog = {"bread": 8, "milk": 6}
print("Result:", fetch_price("milk", catalog))
print()
print("Result:", fetch_price("cake", catalog))Output:
[connection opened]
Found milk
[connection closed]
Result: 6
[connection opened]
No such product: cake
[connection closed]
Result: NoneBreakdown:
catalog = {"bread": 8, "milk": 6}— this is a dict (dictionary): every key ("bread") is mapped to a value (8).prices["milk"]returns 6. We will learn it in depth in the data structures and functions topic. For now it is enough to know that accessing a key that does not exist throws aKeyError.- Notice: there is a
returnboth inexceptand inelse. And still[connection closed]is printed before theResult. That is,finallyruns after thereturnwas “decided”, but before the function really returns. - Without
finally, we would have to writeclose_connection()before everyreturn. Easy to forget, and a lot of duplication.
Common mistake
Mistake 1: putting cleanup after the try/except instead of in finally.
open_connection()
try:
price = prices[name]
except KeyError:
print("missing")
return None
close_connection() # never reached when except returns!In the case of a KeyError, the return leaves the function, and close_connection() does not run. The connection stays open. finally solves exactly this.
Mistake 2: the follow-up code inside the try, and an except that catches the wrong error.
try:
age = int(text)
category = ["child", "adult"][age // 18] # can also raise IndexError... or ValueError?
except ValueError:
print("Age must be a number")If the follow-up line fails, for example with an IndexError for age 40 (40 // 18 = 2, and there is no index 2), the message is completely unrelated. Put only int(text) inside the try, and the follow-up in else.
Mistake 3: thinking that finally “catches” the error. It does not. If there is no matching except, finally runs, and after that the program crashes with a Traceback.
Practice
The exercises use only what was taught in this lesson and the lessons before it. Try on your own first, and only then open the solution.
Exercise 1: Predict the output
Write what will be printed for each call — before running it:
def half(text):
try:
number = int(text)
except ValueError:
print("not a number:", text)
else:
print("half is", number / 2)
finally:
print("checked", text)
half("8")
half("x")Show solution
def half(text):
try:
number = int(text)
except ValueError:
print("not a number:", text)
else:
print("half is", number / 2)
finally:
print("checked", text)
half("8")
half("x")half is 4.0
checked 8
not a number: x
checked x- For
"8"there is no error, soelseruns, followed byfinally. - For
"x"aValueErroris thrown, soexceptruns — andelsedoes not run.finallyruns in both cases.
Exercise 2: finally runs even after return
def check(number):
try:
if number > 0:
return "positive"
return "not positive"
finally:
print("done checking", number)
print(check(5))
print(check(-2))What will be printed, and in which order? Explain why.
Show solution
def check(number):
try:
if number > 0:
return "positive"
return "not positive"
finally:
print("done checking", number)
print(check(5))
print(check(-2))done checking 5
positive
done checking -2
not positivefinally runs before the function really returns — even when there is a return inside the try. That is why “done checking” is printed before the value the function returned.
Exercise 3: The four blocks
Write a function parse_quantity(text) that uses all four blocks:
try— converts the text to a number;except— printsinvalid: <text>;else— printsquantity: <number>;finally— prints---.
parse_quantity("3")
parse_quantity("three")Expected output:
quantity: 3
---
invalid: three
---Show solution
def parse_quantity(text):
try:
quantity = int(text)
except ValueError:
print("invalid:", text)
else:
print("quantity:", quantity)
finally:
print("---")
parse_quantity("3")
parse_quantity("three")quantity: 3
---
invalid: three
---The code that runs only when everything succeeded is in else and not inside the try — that way the try stays small, and catches only the line that can really fail.
Exercise 4: Closing what we opened
The functions open_register() and close_register() open and close a cash register. Write a function sell(prices, position) that opens the register, returns the price at the chosen position (the position is an index of the list — it starts from 0), and always closes the register — even when the position does not exist. A position that does not exist returns None.
def open_register():
print("register opened")
def close_register():
print("register closed")
print(sell([10, 20, 30], 1))
print(sell([10, 20, 30], 7))Expected output:
register opened
register closed
20
register opened
register closed
NoneShow solution
def open_register():
print("register opened")
def close_register():
print("register closed")
def sell(prices, position):
open_register()
try:
return prices[position]
except IndexError:
return None
finally:
close_register()
print(sell([10, 20, 30], 1))
print(sell([10, 20, 30], 7))register opened
register closed
20
register opened
register closed
NoneThat is exactly the job of finally: cleanup — closing a resource that was opened, no matter how the function ended.
Exercise 5: else or inside the try?
Two developers wrote the same code in two ways:
# A # B
try: try:
number = int(text) number = int(text)
print(100 / number) except ValueError:
except ValueError: print("not a number")
print("not a number") else:
print(100 / number)What is the difference, and which version is better? What will happen in each one when text is "0"?
Show solution
- In A, the division is also inside the
try. In B, only the conversion is inside thetry, and the division moved toelse. - When
textis"0", both versions throw aZeroDivisionError, which noexceptcatches, and the program crashes. But in B it is completely clear that theexcept ValueErrorbelongs only to the conversion. - B is better: a
tryas small as possible. That wayexceptdoes not catch by mistake errors that come from other lines, and it is easy to see what each part does.
Summary
try— risky code.except— only if a matching error happened.else— only if there was no error.finally— always.exceptandelsenever run together.finallyruns even afterreturn, and also when the error was not caught (and then the program crashes after it).elsekeeps thetrysmall and focused.finallyis the place for cleanup: closing files and connections.