Home Tutorials Standard Library

Standard Library

Working with Files in Python: Reading, Writing, and Paths

Pyford Notes July 1, 2026 7 min read
Key points
  • Always open files with a with statement so they close automatically.
  • Iterate the file object line by line to avoid loading large files into memory.
  • Use pathlib.Path for path manipulation instead of string concatenation.
  • Specify encoding explicitly (encoding="utf-8") to avoid platform-specific defaults.

open() mode reference

ModeMeaningFile must exist?
"r"Read text (default)Yes
"w"Write text, truncate if existsNo (creates file)
"a"Append textNo (creates file)
"x"Exclusive create, fails if existsNo
"r+"Read and write, no truncateYes
"rb"Read binaryYes
"wb"Write binary, truncateNo

Reading text files

The most common pattern: open, read all lines, close. With with, the close happens automatically even if an exception occurs:

with open("data.txt", encoding="utf-8") as fh:
    content = fh.read()          # entire file as one string

with open("data.txt", encoding="utf-8") as fh:
    lines = fh.readlines()       # list of strings, newlines included

with open("data.txt", encoding="utf-8") as fh:
    for line in fh:              # iterate without loading all lines
        process(line.rstrip())

For large files, iterating the file object directly is the correct approach. A 2 GB log file read with readlines() consumes 2 GB of RAM; iterating it line by line uses only as much memory as one line at a time.

Writing and appending

records = [{"id": 1, "val": "alpha"}, {"id": 2, "val": "beta"}]

with open("output.txt", "w", encoding="utf-8") as fh:
    for rec in records:
        fh.write(f"{rec['id']}\t{rec['val']}\n")

# Append to an existing log
with open("events.log", "a", encoding="utf-8") as fh:
    fh.write(f"[2026-07-01] Startup complete\n")

Mode "w" truncates the file on open. If you want to create a file and guarantee no overwrite, use "x"; it raises FileExistsError if the path already exists, making the intent explicit.

Why the with statement matters

A file object opened without with must be closed manually. If an exception occurs before fh.close(), the file handle leaks. On Windows, leaking handles can prevent other processes from accessing the file:

# Fragile: close may be skipped on exception
fh = open("data.txt")
data = fh.read()
fh.close()

# Robust: context manager guarantees close
with open("data.txt") as fh:
    data = fh.read()

pathlib for path manipulation

pathlib.Path treats file system paths as objects rather than plain strings, which makes joining, checking existence, and finding extensions cleaner:

from pathlib import Path

base = Path("/var/log/app")
log_file = base / "events.log"        # path joining with /
print(log_file.name)                  # "events.log"
print(log_file.stem)                  # "events"
print(log_file.suffix)                # ".log"
print(log_file.exists())              # True / False

for path in base.glob("*.log"):       # find all log files
    print(path)

log_file.write_text("startup\n", encoding="utf-8")   # shorthand write
content = log_file.read_text(encoding="utf-8")        # shorthand read

Path is also the recommended way to build paths portably across Linux, macOS, and Windows. Avoid concatenating path strings with + or hardcoding separators.

Binary files

Open in binary mode ("rb" / "wb") when working with images, archives, or any non-text format. Binary mode yields bytes objects instead of strings:

with open("image.png", "rb") as fh:
    header = fh.read(8)        # first 8 bytes
    rest = fh.read()           # remainder

with open("copy.png", "wb") as out:
    out.write(header + rest)

Practical patterns

Read JSON:

import json
from pathlib import Path

data = json.loads(Path("config.json").read_text(encoding="utf-8"))

Write CSV row by row:

import csv

rows = [["name", "score"], ["Alice", 92], ["Bob", 87]]
with open("scores.csv", "w", newline="", encoding="utf-8") as fh:
    writer = csv.writer(fh)
    writer.writerows(rows)

Note newline="" when using the csv module; the module handles line endings itself and the empty string prevents double newlines on Windows.