Skip to main content

Reading and Writing Files

File I/O crosses a boundary between program state and an external filesystem. Make the data format, encoding, mode, and failure behavior explicit.

Resource lifetime​

Use a context manager so the file is closed even when an exception occurs:

from pathlib import Path

path = Path("notes.txt")
with path.open("r", encoding="utf-8") as handle:
for line in handle:
process(line.rstrip("\n"))

Iterating over the file streams lines. read() and readlines() load the remaining content into memory and should be reserved for bounded inputs. rstrip("\n") removes a line ending without also discarding meaningful spaces as an unrestricted strip() would.

Text and binary modes​

Text mode decodes bytes into str; binary mode returns bytes unchanged.

text = path.read_text(encoding="utf-8")
payload = Path("image.bin").read_bytes()

Specify an encoding for durable text rather than depending on a machine's locale default. Newline translation is another text-mode behavior; pass newline= when a file format requires precise control.

The main modes are:

ModeMeaningExisting file
rreadrequired
wwritetruncated
aappendpreserved
xexclusive createmust not exist

Add b for binary mode or + for combined reading and writing. Treat w as a destructive operation.

Structured formats​

Use a format-aware library rather than hand-built string splitting:

import json

with Path("settings.json").open("r", encoding="utf-8") as handle:
settings = json.load(handle)

Use csv for delimited data and open CSV files with newline="" as its documentation recommends. Parsing does not validate business meaning; check the resulting schema, ranges, and required fields separately.

Failures and safer replacement​

Handle only errors for which the program has a recovery policy, such as FileNotFoundError, PermissionError, decoding errors, or invalid format data. Do not collapse all failures into an empty result.

For important output, write a temporary file in the same directory, flush and close it, then replace the destination. A same-filesystem rename can prevent readers from observing a partially written file, but crash durability may also require filesystem-specific synchronization, such as flushing Python buffers before calling os.fsync. Atomic replacement does not solve concurrent-writer coordination.

A write/read round trip without existing files​

This example uses an isolated temporary directory and cleans it up on exit. The file methods operate from the current stream position, not from the beginning on every call.

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
path = Path(directory) / "notes.txt"
with path.open("x", encoding="utf-8", newline="\n") as handle:
assert handle.write("café\n\n") == 6
with path.open("r", encoding="utf-8") as handle:
assert handle.readline() == "café\n"
assert handle.readline() == "\n" # a blank line, not EOF
assert handle.readline() == "" # EOF
handle.seek(0)
assert handle.read() == "café\n\n"
assert handle.closed

write does not append a newline automatically and returns the number of text characters written, not encoded bytes. Text read(size) counts characters; binary read(size) counts bytes. Text tell() returns an opaque position cookie, not a general-purpose character index. Use seek(0) to restart or a saved tell() value rather than arbitrary byte offsets in a text stream.

With the default newline=None, reading translates \r, \r\n, and \n to \n; a blank line is therefore distinct from end-of-file. Mode a creates a missing file and appends to an existing one; r+ requires an existing file and does not truncate it, while w+ truncates on opening. None creates missing parent directories. Closing a file flushes Python's buffers, but does not by itself promise persistence after a power failure.

Source​

Explore connectionsOpen network