Concepts / File handling best practices

File handling best practices

The finally block executes unconditionally after try and except blocks, making it ideal for resource cleanup.

  • Programming

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?

  • Only code after the entire try and except structure
  • The finally block
  • No cleanup code
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.

entercontinueraiseshandledunhandledthencleanuptryOpen fileRead fileExceptionexceptfinallyClosed file
What happens next when an exception occurs inside the try block, and how does control move through except and finally?

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.

open succeedsexception may occurfinally closesnormal completionNonebefore tryOpenInterruptedClosed
How does the file move from opened to closed even when an exception interrupts the operation?

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.

continuesthenhandledthennot handledalways in normal pathsSuccessfuloperationSuccess codefinallyClose fileExceptionexcept
What is the difference between code that runs only after successful file operations and code that runs regardless of success or failure?

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.

no exceptionhandlednot handledthenthenbefore propagationrunstrySuccessfinallyCleanupCaught exceptionUncaught exception
Which code paths still reach finally when the try block succeeds, raises an exception, or is handled by except?

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

MEDIUM

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 pathDoes a file object exist?Does finally run?Cleanup decision
Open and read succeedYesYesClose the file
Read raises a handled exceptionYes, if opening succeededYesClose the file
Open raises IOErrorNo; the resource remains NoneYesDo not call close

The resource-existence check determines whether finally should close a file.

Reliable Cleanup Checklist

  1. Initialize the file resource variable to None before entering try.
  2. Perform file acquisition and file-dependent operations inside try.
  3. Use except blocks for the exceptions the operation is meant to handle.
  4. Place cleanup in finally because it runs after success and after handled or unhandled exceptions in normal scenarios.
  5. Check that the resource exists before calling close.
  6. 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.