Skip to content
Happy Programming Guide
Start learning
Python

Reading and Writing Files in Python

How to open, read, write and append files in Python using with, plus the path and encoding traps that make files behave differently on someone else’s machine.

Reading and writing files is the step between writing scripts that print things and writing programs that are actually useful. Python makes it short:

Python
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.

Python
# 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#

Python
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. Unlike print(). Add \n yourself.
  • "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#

Python
try:
    with open("settings.txt") as f:
        settings = f.read()
except FileNotFoundError:
    settings = ""          # first run — that is fine

Catch 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.

Python
import os
print(os.getcwd())      # where Python currently thinks it is

To always work next to the script itself, build the path from the script’s own location:

Python
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.

Python
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.

Python
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.

Python
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:

Python
try:
    with open("tasks.json", encoding="utf-8") as f:
        tasks = json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
    tasks = []

Spreadsheet data: CSV#

Python
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:

Python
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#

Try a projectBuild a to-do list that saves its data

Keep reading

Keep going — pick your next guide

The fastest way to improve is to read one guide, then build the thing it describes. Start with the basics, or jump straight to a project.

Ask a question or share what worked

Your email address will not be published. Required fields are marked *