File handling best practices
The file is then opened inside try.
Try it: Context Managers and the with Statement
What with really does: it calls __enter__ before the block and __exit__ after it — even when an exception leaves the block — so a file opened with with is always closed before the exception continues; try/finally gives the same guarantee by hand, a bare close() is skipped by the exception, and a context manager whose __exit__ returns True suppresses the exception.
How it works
- with EXPR as f: evaluates EXPR (e.g. open(...)), calls its __enter__() and binds the returned value to f (a file's __enter__ returns the file itself).
- The block runs. If a statement raises (here int() on text that is not a number), the rest of the block is skipped.
- Whether the block finished or an exception is leaving it, Python calls __exit__(exc_type, exc, tb) — for a file this closes it. This happens before any outer except sees the exception.
- If __exit__ returns a true value the exception is suppressed and execution continues after the block; a file's __exit__ returns None, so the exception propagates to the nearest matching except (or stops the program).
- try: … finally: f.close() gives the same cleanup by hand (start with f = None and check it); open() … f.close() without either leaves the file open when an exception jumps past close().
Default run (10 steps): The program opens data.txt, which contains '4x\n'. Watch when the file is closed — and what happens if int() raises ValueError. … The program finished. The file was closed.
Simplified: Fixed program templates reading one line of data.txt (text you type, up to 12 ASCII characters) and converting it with int(). A small interpreter written for these statements runs them — nothing is executed — and it reproduces CPython 3.12's sys.settrace line/call/exception events, printed output, final error line and file state for every combination. File objects' own __enter__/__exit__ are C code, so they appear as protocol steps rather than traced lines.
Loading the simulation…
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
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.
- 1Initialize
Before try begins, the resource variable is set to None. It is therefore defined even though no file has been opened.
- 2Attempt acquisition
The open operation raises IOError. Because the operation fails immediately, the resource variable is never assigned a file object.
- 3Handle the exception
The matching except block handles the IOError.
- 4Run cleanup
finally executes. Its existence check sees that the resource variable is None, so it does not attempt to close a nonexistent file object.
- 5Avoid a second failure
Because the variable was initialized and checked, the cleanup code does not raise an
AttributeErrorwhile 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
Common mistake Defining the resource variable for the first time inside try
What it looks likeIf open raises IOError immediately, the variable is never assigned.
Why it failsThe finally block may then try to use a variable that does not exist, causing an
AttributeErrorin the cleanup code.Do this insteadInitialize the resource variable to None before try.
Common mistake Calling close without checking whether the resource exists
What it looks likeThe cleanup path calls close even though opening the file failed.
Why it failsA failed acquisition means there is no file object to close.
Do this insteadCheck whether the resource exists before calling close in finally.
Common mistake Putting cleanup only after the file operations
What it looks likeThe close operation appears after the try and except structure rather than in finally.
Why it failsAn exception can interrupt execution before that cleanup line is reached.
Do this insteadPlace resource cleanup in finally so it runs across normal success and exception paths.
Common mistake Assuming finally can never be skipped
What it looks likeThe program is forcefully terminated by os.exit() or a system kill signal.
Why it failsThe source identifies forceful termination as a rare circumstance in which finally may not run.
Do this insteadRely on finally for normal exception-handling scenarios, while recognizing this rare limitation.
Common mistake Allowing finally to raise a new exception
What it looks likeThe cleanup operation itself fails inside finally.
Why it failsThat new exception propagates after finally completes.
Do this insteadKeep 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
- 1Initialize the file resource variable to None before entering try.
- 2Perform file acquisition and file-dependent operations inside try.
- 3Use except blocks for the exceptions the operation is meant to handle.
- 4Place cleanup in finally because it runs after success and after handled or unhandled exceptions in normal scenarios.
- 5Check that the resource exists before calling close.
- 6Remember 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
AttributeErrorduring cleanup. - Forceful termination and exceptions inside finally are important boundaries to the normal guarantee.