else and finally blocks
Overview
Python's exception handling includes two optional blocks: `else` and `finally`. The `else` block runs ONLY if the `try` block completes successfully without raising any exceptions. The `finally` block ALWAYS runs. It executes regardless of whether an exception occurred, whether it was caught, or even if the `try` block contains a `return` statement. The standard pattern assigns specific responsibilities to each block: - `try`: Attempts a single dangerous operation that might fail. - `except`: Handles the specific error if the operation fails. - `else`: Executes the happy-path continuation only if the operation succeeded. - `finally`: Cleans up resources unconditionally at the end. Exceptions raised inside the `else` block are not caught by the preceding `except` blocks. If your happy-path code fails, the error bubbles up: try: data = fetch_data() except NetworkError: print("Fetch failed") else: # If process() raises NetworkError, it will NOT be caught! process(data) This behavior prevents accidentally swallowing errors that occur outside the strictly dangerous operation you meant to protect.
The `finally` block guarantees that critical cleanup happens even if your code crashes halfway through. This is essential for closing database connections, closing file handles, or releasing threading locks. The `else` block improves readability by separating the code that might fail from the rest of the logic. This keeps your `try` blocks perfectly minimized, so you only catch exceptions from the exact line you expect might fail.
Where used: Ensuring database cursors are closed, Cleaning up temporary files after an upload, Releasing threading or async locks
Why learn this
- Preventing database connection leaks by putting `.close()` in a `finally` block
- Minimizing the size of `try` blocks by moving non-risky 'happy path' code to the `else` block
- Understanding that `finally` runs even if you execute a `return` statement inside the `try` block
Code walkthrough
def divide(a, b):
try:
res = a / b
except ZeroDivisionError:
print("Error")
else:
print(f"Result: {res}")
finally:
print("Done")
divide(10, 2)
Focus: Because the division succeeds, the `except` block is skipped, the `else` block prints the result, and the `finally` block executes last to wrap things up.
Aha moment
def test_return():
try:
return "Trying"
finally:
print("Finally runs anyway!")
print(test_return())
Prediction: What prints first: 'Trying' or 'Finally runs anyway!'?
Common guess: `'Trying'`, because the function returns immediately.
Python intercepts the `return` statement in the `try` block. It executes the `finally` block FIRST, and only after the `finally` block completes does it actually return the value `'Trying'` to the caller.
Common mistakes
- Putting everything in the `try` block: If you put 10 lines of code in a `try` block, and a `ValueError` happens, you don't know which line caused it. Put only the one risky line in `try`, and the remaining 9 lines in `else`.
- Returning from `finally`: If you put a `return` statement in a `finally` block, it overrides any `return` or raised exceptions from the `try` or `except` blocks. This leads to extremely confusing bugs where exceptions silently vanish. Never return from `finally`.
Glossary
- happy-path
- The default scenario where everything goes exactly as planned, without any errors or unexpected problems. Example: `return content`
- connection leaks
- When a program opens a connection to a database but fails to close it, eventually exhausting the database's ability to handle new connections. Example: `conn.close()`
Recall questions
- In a `try`/`except` statement, when does the `else` block execute?
- Will the `finally` block execute even if the `try` block contains a `return` statement?
- If an exception is raised inside the `else` block, will the `except` block of the same `try` statement catch it?
Understanding checks
What is the output of this function when called with `divide_and_clean(10, 2)`?
5.0 Done
The `try` block succeeds, so the `except` block is skipped and the `else` block executes, printing the result. Then, the `finally` block ALWAYS executes, printing `'Done'`.
If a `return` statement is executed inside a `try` block, does the `finally` block still run?
Yes, Python ensures the `finally` block is executed before the actual `return` takes place.
The `finally` block guarantees cleanup. Python intercepts the `return`, runs the `finally` block, and then hands the `return` value back to the caller.
Practice tasks
Guaranteed Cleanup
Modify `process_data(data)` so that it catches `ZeroDivisionError` if `100 / data` fails (printing `'Failed'`), prints `'Success'` ONLY if it succeeds, and ALWAYS prints `'Cleanup done'`.
Challenge
Safe file reader
Write `read_file(filename)`. Try to open it and read it. Handle `FileNotFoundError`. Use `else` to return the content. Use `finally` to ensure the file is closed (if it was opened). Note: you can use a mock `File` class with `.close()` for the solution.
Continue learning
Previous: try/except with specific exception types