Home› Tutorials› Standard Library
Standard LibraryWorking with Files in Python: Reading, Writing, and Paths
- Always open files with a
withstatement so they close automatically. - Iterate the file object line by line to avoid loading large files into memory.
- Use
pathlib.Pathfor path manipulation instead of string concatenation. - Specify encoding explicitly (
encoding="utf-8") to avoid platform-specific defaults.
open() mode reference
| Mode | Meaning | File must exist? |
|---|---|---|
"r" | Read text (default) | Yes |
"w" | Write text, truncate if exists | No (creates file) |
"a" | Append text | No (creates file) |
"x" | Exclusive create, fails if exists | No |
"r+" | Read and write, no truncate | Yes |
"rb" | Read binary | Yes |
"wb" | Write binary, truncate | No |
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.