Python · Intermediate
Reading and Writing Files in Python
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
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.
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.
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.
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)
Falsewrite_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
| Mode | Meaning | If the file exists | If it does not |
|---|---|---|---|
| r | read (default) | opens at the start | FileNotFoundError |
| w | write | contents erased on open | created |
| a | append | writes go to the end | created |
| x | create for writing | FileExistsError | created |
| r+ | read and write | opens at the start | FileNotFoundError |
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 · fixwith 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 secondf.read()or afor line in floop 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", soint(line)still works butline == "done"isFalseand printing adds blank lines.Fix: Strip it with
line.rstrip("\n")orline.strip(), or usef.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.
failed = 0
with open("runs.log", encoding="utf-8") as f:
for line in f:
if line.rstrip("\n").endswith("FAILED"):
failed += 1Key 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 ffor large files; usef.read()for small ones. - Lines keep their trailing newline; strip it before comparing or converting.
writeadds no newline;print(text, file=f)does.- Pass the encoding explicitly so files behave the same on every machine.
pathlib.Pathjoins, 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.
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)3with open("notes.txt", "w", encoding="utf-8") as f:
f.write("buy flour\n\ncall the supplier\n\n\nfix the sign\n")
count = 0
with open("notes.txt", encoding="utf-8") as f:
for line in f:
if line.strip():
count += 1
print(count)Practise this
Exercises for this lesson are in the Python practice set.
Related lessons
- Strings in PythonWorking with text in Python: indexing and slicing strings, methods such as strip(), split(), replace() and join(), and formatting output with f-strings.9 min
- Exceptions and Error Handling in PythonHow Python exceptions work: catching specific errors with try/except, using else and finally, raising your own, and defining custom exception classes.10 min
- Modules and Imports in PythonWhat a Python module is, the three forms of import, where Python looks for modules, how __name__ works, and how your own .py file becomes importable.9 min
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.
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.