Concepts / Context managers and the with statement

Context managers and the with statement

The topic combines context managers with the with statement, while the supplied mechanism is explained through the finally guarantee.

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

tryresource-dependent worksuccessnormal continuationexcepthandled exception pathfinallycleanupclosedresource releasedexceptioninterrupted workno exceptionexception occursmatching handlerafter tryafter exceptclose resource
When an exception occurs inside try, which code runs next, and does finally still execute?

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.

not openbefore acquisitionopenfile handle allocatedwork succeedsnormal pathfinallycleanup actionclosedfile handle releasedexceptioninterrupted pathopen filedependent codedependent code interruptedcleanup pathcleanup pathclose file
How does a file move from open to closed whether dependent work succeeds or raises an exception?

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().

Code walkthrough
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()
  1. 1
    f = None creates a safe starting value before any attempt to open the file.
  2. 2
    The open operation assigns a file object to f if acquisition succeeds.
  3. 3
    The loop performs the dependent reading work.
  4. 4
    The except clauses handle the two specific exception paths used in this pattern.
  5. 5
    The finally block is reached after success, a handled exception, or an unhandled exception.
  6. 6
    The condition ensures close is called only when f refers to an acquired resource.
  • The resource variable is initialized before acquisition.
  • Opening the file and reading its lines are resource-dependent operations.
  • IOError handles the file-related failure described in the source.
  • KeyboardInterrupt handles interruption by the user.
  • The cleanup block runs after the try or except path.
  • The existence check prevents closing a file object that was never acquired.
Worked example

Tracing a Failed Open

Assume the file cannot be opened and the open operation raises IOError. Which cleanup actions are safe?

  1. 1
    Before try

    f has the value None, so the cleanup variable already exists even though no file has been acquired.

  2. 2
    During acquisition

    The open operation raises IOError before assigning a file object to f.

  3. 3
    Exception handling

    The IOError handler runs and reports the file-related problem.

  4. 4
    Cleanup

    finally still runs. Its condition is false because f is None, so it does not call close on a nonexistent file object.

Result

The cleanup code completes without creating a second error. Initializing f and checking its value protect the cleanup path when acquisition fails.

normal continuationafter successful trydependent workruns on the normal pathfinallycleanupcloseif resource existsno exceptionafter workresource exists
Which statements run only when no exception occurs, and which statements run regardless of success or failure?

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.

programresourcefile handleindented blockdependent workcleanupresource releaseacquireprovide resourcesuccess or exceptionrelease
What setup and cleanup actions surround the indented resource-dependent block?

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.

not openbefore openopenduring dependent workexceptionnormal path interruptedfinallyclose operationclosedafter cleanupacquireexceptionfinally runsclose
What is the file's state before, during, and after an exception interrupts resource-dependent code?

Mistakes in Cleanup Logic

  • Common mistake

    Putting close() only after the file-processing code.

    What it looks like

    The program reads the file and calls close() afterward, but an exception occurs during the reading loop.

    Why it fails

    The exception can bypass the later close call, leaving the file open.

    Do this instead

    Place the close operation in finally so cleanup is attempted after the normal and exception paths.

  • Common mistake

    Failing to initialize the resource variable before try.

    What it looks like

    The code calls close() in finally even though the open operation might fail before assigning the file variable.

    Why it fails

    The cleanup code may refer to a variable that was never defined, causing an AttributeError.

    Do this instead

    Initialize the variable to None before try.

  • Common mistake

    Closing without checking whether acquisition succeeded.

    What it looks like

    The code calls f.close() unconditionally in finally after open() raises IOError.

    Why it fails

    There is no file object to close when acquisition failed.

    Do this instead

    Check that the resource exists before calling close().

  • Common mistake

    Treating finally as a success-only branch.

    What it looks like

    The code assumes finally runs only when the file loop completes normally.

    Why it fails

    finally also runs when an exception is caught and in normal cases where an exception is not caught.

    Do this instead

    Use finally for cleanup that must occur regardless of the execution path.

Best practice

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

MEDIUM

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?

  • Yes, finally runs after the except path.

  • No, because the exception interrupted the read.

  • Only if the file was opened after the exception.

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

  1. 1
    A file is an operating-system resource that enters an open state when acquired.
  2. 2
    An exception can bypass cleanup written after resource-dependent work.
  3. 3
    The finally block runs after normal execution and exception-handling paths, making it suitable for cleanup.
  4. 4
    Initialize resource variables before try and check that a resource exists before closing it.
  5. 5
    The cleanup guarantee applies to normal exception-handling situations, with limited exceptions such as forceful termination or a new exception raised inside finally.

At a glance

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

Related concepts

More concepts starting with C