Reading and writing files is the step between writing scripts that print things and writing programs that are actually useful. Python makes it short:
with open("notes.txt", "w") as f:
f.write("First line\n")
with open("notes.txt") as f:
print(f.read())The with block closes the file for you, even if something goes wrong inside it. Use it every time and you can forget files exist as a thing to clean up.
Reading#
Three ways, for three situations.
# The whole thing as one string
with open("notes.txt") as f:
text = f.read()
# Every line as a list
with open("notes.txt") as f:
lines = f.readlines()
# One line at a time — best for large files
with open("notes.txt") as f:
for line in f:
print(line.strip())The third form never loads the whole file into memory, so it works on a file too big to fit. It is also the one you will use most.
.strip() is there because each line still carries its newline character. Forgetting it produces output with mysterious double spacing.
Writing#
with open("notes.txt", "w") as f:
f.write("Line one\n")
f.write("Line two\n")Two things surprise people here:
write()does not add a newline. Unlikeprint(). Add\nyourself."w"empties the file first. Not appends — empties. If the file had a thousand lines, they are gone the moment you open it.
The modes#
| Mode | Does | If the file is missing |
|---|---|---|
"r" |
Read (the default) | FileNotFoundError |
"w" |
Write, emptying it first | Creates it |
"a" |
Append to the end | Creates it |
"x" |
Write, but only if new | Creates it; errors if it exists |
"x" is genuinely useful when overwriting would be a bug — it makes Python refuse rather than silently destroy.
Handling a file that is not there#
try:
with open("settings.txt") as f:
settings = f.read()
except FileNotFoundError:
settings = "" # first run — that is fineCatch the specific error, not everything. See Python try/except explained.
Why your file ended up somewhere unexpected#
A plain filename like "notes.txt" is relative to the folder your terminal is in, not the folder the script lives in. Run the same script from a different directory and it reads and writes somewhere else.
import os
print(os.getcwd()) # where Python currently thinks it isTo always work next to the script itself, build the path from the script’s own location:
from pathlib import Path
HERE = Path(__file__).parent
notes = HERE / "notes.txt"
with open(notes) as f:
print(f.read())pathlib also joins paths correctly on every operating system, so you never hand-write a slash and break it on Windows.
from pathlib import Path
p = Path("data") / "2026" / "scores.txt"
print(p.exists())
print(p.suffix) # .txt
print(p.stem) # scores
p.parent.mkdir(parents=True, exist_ok=True) # make the folders
print(p.read_text()) # shortcut for small files
p.write_text("hello")Encoding: the trap that only appears on someone else’s machine#
Python picks a default text encoding from your system, and on some Windows setups that is not UTF-8. Your file works for you and produces UnicodeDecodeError for someone else — or quietly mangles any accented character or emoji.
with open("notes.txt", encoding="utf-8") as f:
text = f.read()
with open("notes.txt", "w", encoding="utf-8") as f:
f.write("café ☕")Get into the habit of writing encoding="utf-8" on every text file you open. It costs nothing and removes an entire class of bug.
Structured data: use JSON, not your own format#
The moment you want to store more than lines of text — a list of tasks, a dictionary of settings — reach for JSON rather than inventing a separator.
import json
tasks = [
{"text": "Buy milk", "done": True},
{"text": "Learn files", "done": False},
]
with open("tasks.json", "w", encoding="utf-8") as f:
json.dump(tasks, f, indent=2)
with open("tasks.json", encoding="utf-8") as f:
loaded = json.load(f)
print(loaded[0]["text"])indent=2 makes the file readable if you open it yourself, which is worth the extra bytes while you are learning. This is exactly what the to-do list project does.
Guard the load, because a half-written or hand-edited file will not parse:
try:
with open("tasks.json", encoding="utf-8") as f:
tasks = json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
tasks = []Spreadsheet data: CSV#
import csv
with open("scores.csv", encoding="utf-8", newline="") as f:
for row in csv.DictReader(f):
print(row["name"], row["score"])Use the csv module rather than splitting on commas yourself — a field containing a comma inside quotes will break the naive version, and real data always contains one eventually.
The newline="" looks odd and is required; without it you get blank rows on Windows.
A worked example#
A tiny logger that appends a timestamped line every time it runs, then reports how many lines it has:
from datetime import datetime
from pathlib import Path
LOG = Path(__file__).parent / "log.txt"
with open(LOG, "a", encoding="utf-8") as f:
f.write(f"{datetime.now():%Y-%m-%d %H:%M} — ran\n")
with open(LOG, encoding="utf-8") as f:
lines = [line.strip() for line in f if line.strip()]
print(f"{len(lines)} runs recorded")
print("Last:", lines[-1])Append mode, a path anchored to the script, explicit encoding, and one read into memory. That is the whole pattern for most small tools.
Questions people ask#
Do I have to close the file?
If you use with, no — it closes automatically, including when an exception is raised. Without with you must call f.close() yourself, which is why with is the standard.
What is the difference between read() and readlines()?
read() gives you one big string; readlines() gives you a list with one item per line, newlines included. Looping over the file object directly is usually better than either.
How do I delete or rename a file?
Path("old.txt").rename("new.txt") and Path("junk.txt").unlink(). Check .exists() first, or catch FileNotFoundError.
Can I edit one line in the middle of a file?
Not directly. Read the file into a list, change the item you want, and write the whole thing back. Text files have no concept of inserting in the middle.
What about very large files?
Loop over the file object one line at a time and never call read() or readlines(). That keeps memory use flat no matter how big the file is.
Where to go next#
- Python virtual environments — keeping each project’s packages separate
- Build a to-do list that saves to a file
- Python dictionaries explained — the shape JSON loads into