File handling best practices
The finally block executes unconditionally after try and except blocks, making it ideal for resource cleanup.
Why Cleanup Needs a Guarantee
Opening a file asks the operating system to allocate a resource called a file handle. The file enters an open state, and it must eventually be closed. If the program crashes, raises an exception, or is interrupted before closing the file, the resource can remain open. This wastes system memory and may prevent other programs from accessing the file. Cleanup written only after the file operations can be skipped when an exception interrupts execution.
Tracing an Interrupted Read
What do you think happens?
A file has been opened inside try. An exception interrupts the file-reading operation, and an except block handles it. Which part still runs afterward?
Reveal answer
Answer: The finally block
finally runs even when an exception is caught by except. Its purpose is to ensure cleanup happens regardless of the execution path through try and except.
The important path is not limited to successful reading. If reading raises an exception, control moves into the matching except block when that exception is handled. Afterward, control reaches finally. If the exception is not caught, finally still executes before the exception continues. In both cases, the cleanup step remains part of the path.
The Resource-Safe Pattern
A resource-safe try..except..finally structure begins by initializing the resource variable to None before entering try. The file is then opened inside try. If opening succeeds, the variable refers to the file resource. If opening fails with an IOError, the variable remains None because no file object was assigned. The finally block checks whether the resource exists before attempting to close it.
Always initialize the resource variable before try and check whether it exists before calling close in finally. This protects the cleanup code when resource acquisition itself fails.
Tracing a Failed File Open
Trace the resource variable when opening a file raises an IOError before any file object is created.
Initialize: Before try begins, the resource variable is set to None. It is therefore defined even though no file has been opened.
Attempt acquisition: The open operation raises IOError. Because the operation fails immediately, the resource variable is never assigned a file object.
Handle the exception: The matching except block handles the IOError.
Run cleanup: finally executes. Its existence check sees that the resource variable is None, so it does not attempt to close a nonexistent file object.
Avoid a second failure: Because the variable was initialized and checked, the cleanup code does not raise an AttributeError while trying to close an unassigned variable.
The failed acquisition is handled safely, and the cleanup step does not create a new error.
Success Code and Cleanup Code
Code that belongs in the successful-operation path should run only when the file operation completes successfully. Cleanup belongs in finally because it must run after success, after a caught exception, and after an uncaught exception in normal exception-handling scenarios. This distinction prevents cleanup from depending on the success of the operation it is meant to clean up.
For example, reading each line of a file is part of the try operation. Handling an IOError or KeyboardInterrupt belongs to the corresponding except paths. Closing the file belongs in finally because it is required whether the reading succeeds, an expected exception is handled, or another exception interrupts the operation.
Mistakes That Break Cleanup
Defining the resource variable for the first time inside try
The finally block may then try to use a variable that does not exist, causing an AttributeError in the cleanup code.
Fix:
Initialize the resource variable to None before try.Calling close without checking whether the resource exists
A failed acquisition means there is no file object to close.
Fix:
Check whether the resource exists before calling close in finally.Putting cleanup only after the file operations
An exception can interrupt execution before that cleanup line is reached.
Fix:
Place resource cleanup in finally so it runs across normal success and exception paths.Assuming finally can never be skipped
The source identifies forceful termination as a rare circumstance in which finally may not run.
Fix:
Rely on finally for normal exception-handling scenarios, while recognizing this rare limitation.Allowing finally to raise a new exception
That new exception propagates after finally completes.
Fix:
Keep the cleanup logic safe by initializing and checking the resource before closing it.
Practice the Execution Trace
Consider three runs of the same file-handling structure: one in which the file is opened and read successfully, one in which reading raises an exception handled by except, and one in which opening fails before a file object exists. For each run, identify whether the file resource exists and whether finally must attempt to close it.
Hints
- Start with the value of the resource variable before try.
- Ask whether open succeeded before deciding whether a file object exists.
- Remember that finally is reached after success and after handled exceptions.
| Execution path | Does a file object exist? | Does finally run? | Cleanup decision |
|---|---|---|---|
| Open and read succeed | Yes | Yes | Close the file |
| Read raises a handled exception | Yes, if opening succeeded | Yes | Close the file |
| Open raises IOError | No; the resource remains None | Yes | Do not call close |
The resource-existence check determines whether finally should close a file.
Reliable Cleanup Checklist
- Initialize the file resource variable to None before entering try.
- Perform file acquisition and file-dependent operations inside try.
- Use except blocks for the exceptions the operation is meant to handle.
- Place cleanup in finally because it runs after success and after handled or unhandled exceptions in normal scenarios.
- Check that the resource exists before calling close.
- Remember that forceful termination and exceptions inside finally are rare boundaries to the guarantee.
The essential mental model is a state transition: a successfully opened file is an open resource, and finally performs the cleanup transition to closed even when an exception interrupts the file operation. Initializing the resource to None and checking it before closing prevents the cleanup code from failing when opening never succeeded.
Key Takeaways
- finally executes after try and except across normal success and exception paths.
- File cleanup belongs in finally because exceptions can bypass code placed after file operations.
- Initialize the resource variable to None before try so failed acquisition leaves a safe value.
- Check that the resource exists before calling close to avoid an AttributeError during cleanup.
- Forceful termination and exceptions inside finally are important boundaries to the normal guarantee.