Python · Intermediate

Reading and Writing Files in Python

9 min readUpdated September 24, 2026Every example verified

In short: Python opens a file with open(path, mode, encoding=...) and the with statement closes it automatically when the block ends. Mode r reads, w creates or overwrites, a appends. Iterate over the file object to read it line by line without loading the whole file into memory.

Opening, modes and encodings

Programs keep their results by writing files and get their input by reading them. Python treats a file as an object returned by open(): you read from it, write to it, and close it when you are done. The with statement does the closing for you, even if an exception is raised inside the block, which is why almost every file operation in Python is written as with open(...) as f:.

The second argument to open is the mode. "r" (the default) reads and fails with FileNotFoundError if the file does not exist. "w" writes, creating the file or erasing an existing one the moment it is opened. "a" appends to the end, creating the file if needed. "x" creates a new file and refuses to touch an existing one. Add "b" for binary data such as images, where you get bytes instead of str.

Text files are sequences of characters, but disks store bytes, so an encoding translates between the two. Pass encoding="utf-8" explicitly: the default otherwise depends on the operating system's settings, and a file written on one machine can come out garbled on another. UTF-8 is the right choice for almost everything.

Reading offers three levels of granularity. f.read() returns the whole file as one string, which is simplest for small files. f.readlines() returns a list of lines, each still ending with its newline. Iterating directly with for line in f: yields one line at a time and never holds more than one in memory, which is the right way to process a large log or data file. In every case the newline character stays attached, so line.strip() or line.rstrip("\n") is usually the first thing you do with a line.

Writing is symmetrical: f.write(text) writes exactly the string you give it with no newline added, so you supply "\n" yourself. print(..., file=f) is a convenient alternative that adds the newline for you. Written data may sit in a buffer until the file is closed, another reason to let with close it before you read the file back.

The pathlib module represents paths as Path objects with useful methods: Path("data") / "scores.csv" joins parts correctly on every operating system, path.exists(), path.suffix and path.name inspect it, and path.read_text() and path.write_text() handle small files without an explicit open.

Syntax

 Python · syntax
with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("first line\n")
    print("second line", file=f)

with open("notes.txt", encoding="utf-8") as f:   # mode "r" is the default
    for line in f:
        print(line.rstrip("\n"))

with open("notes.txt", "a", encoding="utf-8") as f:
    f.write("appended\n")

from pathlib import Path
text = Path("notes.txt").read_text(encoding="utf-8")

The file is closed as soon as the with block ends, whether the block finished normally or raised an exception.

Writing a file and reading it back

A shop log is written line by line, then read back as one string. The repr shows exactly which characters ended up in the file.

 Python
lines = ["09:00 open shop", "09:15 first customer", "12:30 lunch break"]
with open("shop_log.txt", "w", encoding="utf-8") as f:
    for line in lines:
        f.write(line + "\n")

with open("shop_log.txt", encoding="utf-8") as f:
    content = f.read()
print(repr(content))
print(content.count("\n"), "lines")

Output

'09:00 open shop\n09:15 first customer\n12:30 lunch break\n'
3 lines

write adds nothing of its own, so each line is written with an explicit "\n"; without it the three entries would run together on one line. The first with block closes the file, which flushes the buffer to disk before the second block opens it for reading. repr reveals the newline characters that print would otherwise render as line breaks.

Appending and processing line by line

Two days of visitor counts are written, a third is appended, and the file is then summed one line at a time.

 Python
with open("visits.txt", "w", encoding="utf-8") as f:
    f.write("Mon 14\nTue 9\n")

with open("visits.txt", "a", encoding="utf-8") as f:
    f.write("Wed 21\n")

total = 0
with open("visits.txt", encoding="utf-8") as f:
    for line in f:
        day, count = line.split()
        total += int(count)
        print(f"{day}: {count}")
print("total", total)

Output

Mon: 14
Tue: 9
Wed: 21
total 44

Opening with "a" keeps Monday and Tuesday and adds Wednesday after them; opening with "w" again would have erased the first two days. The reading loop gets each line with its trailing newline, and split() with no argument discards that whitespace along with the space between the fields. This loop would work unchanged on a file with a million lines, because only one line is in memory at a time.

Paths with pathlib

A small CSV file is created, inspected and parsed through a Path object, then deleted.

 Python
from pathlib import Path

path = Path("scores.csv")
path.write_text("name,score\nAmara,82\nBenoit,67\nChidi,91\n", encoding="utf-8")

print(path.exists(), path.suffix, path.name)
rows = path.read_text(encoding="utf-8").splitlines()
header, *records = rows
print(header.split(","))

best = None
for record in records:
    name, score = record.split(",")
    if best is None or int(score) > best[1]:
        best = (name, int(score))
print(best)

path.unlink()
print(path.exists())

Output

True .csv scores.csv
['name', 'score']
('Chidi', 91)
False

write_text and read_text open, transfer and close in one call, which suits small files. splitlines() removes the newlines that split("\n") would leave as an empty last element. The header line is separated from the data rows by starred unpacking, and each data row is split on the comma. unlink() deletes the file, as the final exists() confirms. For CSV files with quoted fields, the standard library's csv module handles the parsing.

File modes

ModeMeaningIf the file existsIf it does not
rread (default)opens at the startFileNotFoundError
wwritecontents erased on opencreated
aappendwrites go to the endcreated
xcreate for writingFileExistsErrorcreated
r+read and writeopens at the startFileNotFoundError

Common mistakes

  • Opening with "w" to add a record

    Why it goes wrong: Mode "w" truncates the file the moment it is opened, so the previous contents are gone before you write anything.

    Fix: Use "a" to append. Reserve "w" for files you intend to rebuild from scratch.

  • Forgetting to close the file

    Why it goes wrong: Data can stay in a buffer and never reach the disk, and on some systems an open file cannot be deleted or renamed. Relying on garbage collection to close it is not guaranteed.

    Fix: Always use with open(...) as f:; the file is closed when the block ends, even on an exception.

     Python · fix
    with open("log.txt", "a", encoding="utf-8") as f:
        f.write("entry\n")
  • Reading the same file object twice

    Why it goes wrong: After f.read() the position is at the end of the file, so a second f.read() or a for line in f loop yields nothing.

    Fix: Store the result the first time, or call f.seek(0) to return to the beginning.

  • Keeping the newline on each line

    Why it goes wrong: Lines from a file end with "\n", so int(line) still works but line == "done" is False and printing adds blank lines.

    Fix: Strip it with line.rstrip("\n") or line.strip(), or use f.read().splitlines().

Where you use this

Log files, exports and configuration are the daily reasons to touch a file. A script that runs every night can append one line per run to a log with mode "a", and the same script can read that log later to summarise how many runs failed. A report that someone opens in a spreadsheet is a text file with commas between the fields, written line by line; the csv module handles quoting for you when values contain commas.

Reading line by line matters as soon as files stop being small. A sensor that writes one reading per second produces tens of millions of lines a year; for line in f processes such a file with constant memory, while f.read() would try to hold the whole thing at once.

 Python · in practice
failed = 0
with open("runs.log", encoding="utf-8") as f:
    for line in f:
        if line.rstrip("\n").endswith("FAILED"):
            failed += 1

Key points

  • with open(path, mode, encoding="utf-8") as f: opens a file and guarantees it is closed.
  • Mode "r" reads, "w" overwrites, "a" appends, "x" creates only if absent.
  • Iterate with for line in f for large files; use f.read() for small ones.
  • Lines keep their trailing newline; strip it before comparing or converting.
  • write adds no newline; print(text, file=f) does.
  • Pass the encoding explicitly so files behave the same on every machine.
  • pathlib.Path joins, inspects, reads and writes paths without string manipulation.

Try it yourself

The program writes a few lines to notes.txt, some of them blank. Read the file back and print how many lines contain text, not counting the blank ones.

Your program
with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("buy flour\n\ncall the supplier\n\n\nfix the sign\n")

# read notes.txt and count the non-blank lines
print(0)
Expected output: 3

Practise this

Exercises for this lesson are in the Python practice set.

Open the Python playground

Frequently asked questions

What is the difference between read, readline and readlines?

read() returns the whole remaining file as one string. readline() returns the next line including its newline, or an empty string at the end of the file. readlines() returns a list of all remaining lines. For most processing, iterating with for line in f is better than any of them because it reads one line at a time.

Do I need to close a file if I use with?

No. The with statement calls close() automatically when the block ends, whether it ends normally or through an exception. That is its purpose, and it is why an explicit f.close() is rarely seen in modern Python.

How do I check whether a file exists before opening it?

Use pathlib.Path(name).exists() or os.path.exists(name). Alternatively, open it inside a try block and handle FileNotFoundError, which avoids the small window in which the file could disappear between the check and the open.

Progress is stored only in this browser.

How this page was checked. Every program on it was run with CPython 3.11 at build time; runs in your browser on CPython 3.14 (Pyodide) by the publishing checks, and the output shown is what it printed. The Run buttons execute the same code on your device; nothing you type is sent anywhere.