Context managers and the with statement
The finally block executes unconditionally after try and except blocks, making it ideal for resource cleanup.
Why Cleanup Cannot Wait
Opening a file asks the operating system to allocate a resource called a file handle. The file begins in an open state. If an exception occurs before the file is closed, the handle can remain open. This resource leak wastes system memory and can prevent other programs from accessing the file.
The difficulty is that an exception can interrupt the normal path at many points. Cleanup written only after the file operations may never be reached. The finally block addresses this problem because it executes after the try and except parts regardless of whether the protected work succeeds or raises an exception.
The Cleanup Guarantee
The finally block is the part of the structure reserved for work that must be attempted after the try and except paths. It runs when the try block succeeds, when an exception is caught by an except block, and when an exception is not caught. This makes it suitable for closing files, releasing locks, and freeing other resources.
The distinction is between outcome-dependent work and cleanup work. Code placed in the normal continuation path is reached only when the protected operation completes without an exception. Code in finally is intended to run regardless of that outcome. It should therefore contain the action that returns the resource from its open state to its closed state.
A Safe File Pattern
A safe pattern prepares the resource variable before entering try. In this example, f starts as None. If opening the file fails immediately, f is still defined and its value shows that no file object was acquired. The finally block checks that value before calling close().
f = None try: f = open("records.txt") for line in f: process(line) except IOError: report_missing_file() except KeyboardInterrupt: report_interruption() finally: if f is not None: f.close()
Tracing a Failed Open
Assume the file cannot be opened and the open operation raises IOError. Which cleanup actions are safe?
Before try: f has the value None, so the cleanup variable already exists even though no file has been acquired.
During acquisition: The open operation raises IOError before assigning a file object to f.
Exception handling: The IOError handler runs and reports the file-related problem.
Cleanup: finally still runs. Its condition is false because f is None, so it does not call close on a nonexistent file object.
The cleanup code completes without creating a second error. Initializing f and checking its value protect the cleanup path when acquisition fails.
Context Handling Around the Block
The topic combines context managers with the with statement, while the supplied mechanism is explained through the finally guarantee. The central resource-handling idea is that setup, dependent work, and cleanup must be treated as one controlled lifecycle. The cleanup part must not depend on the dependent work completing normally.
The important reasoning habit is to trace both outcomes: the normal path and the interrupted path. In either path, identify where cleanup occurs and whether the resource was successfully acquired before attempting to close it.
Mistakes in Cleanup Logic
Putting close() only after the file-processing code.
The exception can bypass the later close call, leaving the file open.
Fix:
Place the close operation in finally so cleanup is attempted after the normal and exception paths.Failing to initialize the resource variable before try.
The cleanup code may refer to a variable that was never defined, causing an AttributeError.
Fix:
Initialize the variable to None before try.Closing without checking whether acquisition succeeded.
There is no file object to close when acquisition failed.
Fix:
Check that the resource exists before calling close().Treating finally as a success-only branch.
finally also runs when an exception is caught and in normal cases where an exception is not caught.
Fix:
Use finally for cleanup that must occur regardless of the execution path.
For resource cleanup, initialize the resource variable before try, acquire the resource inside try, handle the relevant exception paths, and check that the resource exists before closing it in finally.
Trace the Two Paths
Trace a file operation twice: first assuming the file opens and all dependent work succeeds, then assuming an exception occurs during the dependent work. For each trace, identify which part handles the outcome and which part closes the resource.
Hints
- Start with the value of the resource variable before try.
- Ask whether acquisition succeeded before deciding whether close is safe.
- Remember that finally follows both the normal try path and the except path.
What do you think happens?
A file opens successfully, an exception occurs while reading it, and the exception is caught. Does the cleanup code in finally run?
Reveal answer
Answer: Yes, finally runs after the except path.
The finally block executes even when an exception is caught, so it can close the file after the interrupted operation.
Key Takeaways
- A file is an operating-system resource that enters an open state when acquired.
- An exception can bypass cleanup written after resource-dependent work.
- The finally block runs after normal execution and exception-handling paths, making it suitable for cleanup.
- Initialize resource variables before try and check that a resource exists before closing it.
- The cleanup guarantee applies to normal exception-handling situations, with limited exceptions such as forceful termination or a new exception raised inside finally.
Key Takeaways
- Use finally for cleanup that must happen whether protected work succeeds or fails.
- Initialize a resource variable to None before try so cleanup remains safe if acquisition fails.
- Check that the resource exists before calling close().
- Trace both the success path and the exception path to verify that the resource reaches the closed state.