Concepts / File handling best practices

File handling best practices

The file is then opened inside try.

  • Programming
Interactive lab

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

  1. 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).
  2. The block runs. If a statement raises (here int() on text that is not a number), the rest of the block is skipped.
  3. 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.
  4. 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).
  5. 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.

Educational simulation

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?

  • 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.

tryOpen fileRead fileExceptionexceptfinallyClosed fileentercontinueraiseshandledunhandledthencleanup
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.

Nonebefore tryOpenInterruptedClosedopen succeedsexception may occurfinally closesnormal completion
How does the file move from opened to closed even when an exception interrupts the operation?
Best practice

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.

Worked example

Tracing a Failed File Open

Trace the resource variable when opening a file raises an IOError before any file object is created.

  1. 1
    Initialize

    Before try begins, the resource variable is set to None. It is therefore defined even though no file has been opened.

  2. 2
    Attempt acquisition

    The open operation raises IOError. Because the operation fails immediately, the resource variable is never assigned a file object.

  3. 3
    Handle the exception

    The matching except block handles the IOError.

  4. 4
    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.

  5. 5
    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.

Result

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.

SuccessfuloperationSuccess codefinallyClose fileExceptionexceptcontinuesthenhandledthennot handledalways in normal paths
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.

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

Mistakes That Break Cleanup

  • Common mistake

    Defining the resource variable for the first time inside try

    What it looks like

    If open raises IOError immediately, the variable is never assigned.

    Why it fails

    The finally block may then try to use a variable that does not exist, causing an AttributeError in the cleanup code.

    Do this instead

    Initialize the resource variable to None before try.

  • Common mistake

    Calling close without checking whether the resource exists

    What it looks like

    The cleanup path calls close even though opening the file failed.

    Why it fails

    A failed acquisition means there is no file object to close.

    Do this instead

    Check whether the resource exists before calling close in finally.

  • Common mistake

    Putting cleanup only after the file operations

    What it looks like

    The close operation appears after the try and except structure rather than in finally.

    Why it fails

    An exception can interrupt execution before that cleanup line is reached.

    Do this instead

    Place resource cleanup in finally so it runs across normal success and exception paths.

  • Common mistake

    Assuming finally can never be skipped

    What it looks like

    The program is forcefully terminated by os.exit() or a system kill signal.

    Why it fails

    The source identifies forceful termination as a rare circumstance in which finally may not run.

    Do this instead

    Rely on finally for normal exception-handling scenarios, while recognizing this rare limitation.

  • Common mistake

    Allowing finally to raise a new exception

    What it looks like

    The cleanup operation itself fails inside finally.

    Why it fails

    That new exception propagates after finally completes.

    Do this instead

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

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.

Related concepts

More concepts starting with F