Quick summary
Summarize this blog with AI
Use with open(...) when your code owns a file. The context manager closes it when the block finishes, including when the block raises an exception. A closed file releases its operating-system handle; leaving many files open can exhaust handles and delay buffered writes.
These Python 3.12.14 examples create only temporary files. The same ownership rule applies to a real report: close what you open, and do not close a stream that a caller still owns.
Prove closure after success and failure
from pathlib import Path
from tempfile import TemporaryDirectory
with TemporaryDirectory() as directory:
path = Path(directory) / "report.txt"
with path.open("w", encoding="utf-8") as output:
output.write("café\n")
print("success closed:", output.closed)
try:
with path.open("r", encoding="utf-8") as source:
print(source.readline().strip())
raise ValueError("analysis failed")
except ValueError as error:
print(str(error))
print("failure closed:", source.closed)
try:
source.read()
except ValueError:
print("cannot read a closed file")
Expected output:
success closed: True
café
analysis failed
failure closed: True
cannot read a closed file
The exception still propagates out of the with block. Closure is cleanup, not error suppression, and it does not undo data already written. Open itself can fail before a file object exists; an inaccessible path remains an error. Catch an exception only where you can handle it meaningfully.
Process text without retaining the entire file
from io import StringIO
def sum_lines(stream):
total = 0
for line_number, line in enumerate(stream, start=1):
text = line.strip()
if not text:
continue
try:
value = int(text)
except ValueError as error:
raise ValueError(f"line {line_number}: expected an integer") from error
total += value
return total
with StringIO("10\n\n20\n") as stream:
print(sum_lines(stream))
print("caller still owns stream:", not stream.closed)
with StringIO("") as stream:
print(sum_lines(stream))
with StringIO("10\noops\n") as stream:
try:
sum_lines(stream)
except ValueError as error:
print(str(error))
Expected output:
30
caller still owns stream: True
0
line 2: expected an integer
Iteration avoids a list of every line, but a single extremely long line can still consume substantial memory. For an untrusted upload, enforce byte and line-length limits at ingestion as well. Specify a text encoding; use binary mode when bytes are the required interface. A parser should not silently skip malformed measurements just to finish a file.
Separate flush, close, and durable publication
| Operation | What it establishes | What it does not establish |
|---|---|---|
| flush() | Pushes Python write buffers to the underlying stream. | Survival of a power loss. |
| close() | Flushes a writable file and releases its handle; later I/O fails. | A transaction or reversal of partial output. |
| os.fsync(file.fileno()) | Requests synchronization of the file descriptor after flush. | Portable atomic publication or a guarantee about every filesystem and directory entry. |
If partial reports must never be published, write a staging file, validate it, and use an appropriate atomic publication design. That is a separate requirement from closing a handle. A context manager can itself raise when flushing or closing fails; do not replace that failure with a success message.
Use explicit close only when ownership demands it
A try/finally that closes a successfully opened handle is useful when a context manager cannot express the lifetime. Avoid relying on garbage collection or interpreter shutdown for timely cleanup: implementation and exception paths matter. Never close sys.stdout just because a helper writes to it. For multiple files, give each owned handle a context manager so later open failures also close earlier handles.