Chapter 2
Reading Text
Chapter 1 handed you the whole file as one string with f.read(). That's
occasionally what you want, and often not. Text files are usually made of lines,
and most jobs work on one line at a time: count them, filter them, pull a value
out of each. This chapter is the ways to get at a file's text in the shape you
actually need.
Everything here is still plain text. The reading log looks like a spreadsheet, but to Python it's a run of characters with newlines in it, the same as a note.
The whole file at once
f.read() with no argument returns everything from the current position to the
end, as a single string. Use it when the file is small and you genuinely need
all of it together, for example to search the text for a word:
with open("notes/piranesi.txt") as f:
note = f.read()
print("Mentions the journal:", "journal" in note)
print("Lines in the note:", len(note.splitlines()))
Mentions the journal: True
Lines in the note: 11note is one string. The line breaks are still in there as \n characters, and
note.splitlines() cuts the string on them into a list.
The catch is size. f.read() pulls the entire file into memory. For a note
that's nothing. For a log file that's been growing for two years it's a problem,
and the last section of this chapter is about what to do instead.
One line at a time
A file object is iterable. Loop over it and you get its lines, one per turn:
with open("reading-log.csv") as f:
line_count = 0
for line in f:
line_count += 1
print(f"{line_count} lines, so {line_count - 1} books")
11 lines, so 10 booksThis is the form you'll reach for most. It reads one line, gives it to you, then reads the next, so it never holds more than a single line in memory no matter how big the file is.
A line here means the text up to and including the next newline character. That last part matters more than you'd think.
The trailing newline
Each line the loop gives you keeps its \n on the end. You can see it with
repr, which shows the characters the way Python would write them:
with open("reading-log.csv") as f:
lines = f.readlines()
print(repr(lines[0]))
print(repr(lines[1]))
'title,author,rating,finished\n'
'The Left Hand of Darkness,Ursula K. Le Guin,5,2025-11-02\n'If you print a line as-is, the \n in the string plus the newline print adds
gives you a blank line between every row. The fix is str.rstrip, which removes
trailing whitespace. Pass it "\n" to remove only the newline and leave any real
trailing spaces alone:
with open("reading-log.csv") as f:
for line in f:
print(line.rstrip("\n"))
title,author,rating,finished
The Left Hand of Darkness,Ursula K. Le Guin,5,2025-11-02
Project Hail Mary,Andy Weir,4,2026-01-15
"Rendezvous with Rama, and Other Stories",Arthur C. Clarke,4,2026-02-20
One Hundred Years of Solitude,Gabriel Garcia Marquez,5,2026-03-30
The Peripheral,William Gibson,3,2026-05-11
Klara and the Sun,Kazuo Ishiguro,4,2026-06-01
Piranesi,Susanna Clarke,5,2026-07-19
Recursion,Blake Crouch,,
A Memory Called Empire,Arkady Martine,4,2026-08-25
The Three-Body Problem,Liu Cixin,,Bare line.strip() with no argument also works and is common, but it strips
leading whitespace too, which isn't always what you want. When in doubt,
rstrip("\n") does exactly one thing.
readline and readlines
Two more methods, both occasionally the right tool.
f.readline() reads a single line and advances the position. It's useful when
the first line is special, like a header, and you want to handle it before the
loop:
with open("reading-log.csv") as f:
header = f.readline()
print("Columns:", header.rstrip("\n"))
books = 0
for line in f:
books += 1
print("Books:", books)
Columns: title,author,rating,finished
Books: 10The for loop picks up from line two, because readline already consumed line
one.
f.readlines() reads the whole file and returns a list of lines. It costs the
same memory as f.read(), plus the overhead of the list. Reach for it only when
you truly need all the lines as a list at once, for instance to walk them in
reverse. For everything else, the plain for line in f loop is lighter.
Pulling a value out of a line
So far we've worked with whole lines. Usually you want one piece of a line: the title, or the rating. The obvious move is to split the line on commas:
with open("reading-log.csv") as f:
next(f) # skip the header
for line in f:
title = line.split(",")[0]
print(title)
The Left Hand of Darkness
Project Hail Mary
"Rendezvous with Rama
One Hundred Years of Solitude
The Peripheral
Klara and the Sun
Piranesi
Recursion
A Memory Called Empire
The Three-Body ProblemLook at the third line. That book's title is Rendezvous with Rama, and Other
Stories. It has a comma in it, so the file wraps it in quotes, and
line.split(",") cut it in half at the wrong comma.
Reading a file line by line is fine. Carving those lines into fields by hand is
where it goes wrong, and it goes wrong quietly. Chapter 7 is about the csv
module, which handles the quoting and gives you the fields correctly. Until then,
stick to whole lines.
(next(f) there is another way to skip the header: it asks the file object for
its next line and throws it away.)
Big files: read what you need, not the whole thing
The for line in f loop is the default because of what comes next. Build a log with a
hundred thousand rows in it:
with open("big-log.csv", "w") as f:
f.write("title,author,rating,finished\n")
for n in range(100_000):
f.write(f"Book {n},Author {n},{n % 6},2026-06-01\n")
Now count how many are rated 5, without ever holding more than one line:
rated_five = 0
with open("big-log.csv") as f:
next(f) # skip the header
for line in f:
fields = line.rstrip("\n").split(",")
if fields[2] == "5":
rated_five += 1
print(f"{rated_five} books rated 5")
16666 books rated 5The generated titles have no commas in them, so split(",") is safe here. The
point is the loop. It reads one line, checks it, discards it, and moves on. Swap
that loop for f.read() or f.readlines() and you'd load the whole file, which
is fine at a hundred thousand rows and falls over at a hundred million.
Reading a fixed-size chunk with f.read(size) in a loop is another option for
files that aren't line-based at all, like binary data. Chapter 10 covers that.
Common mistakes
Reading the same file twice. After f.read(), the position is at the end of
the file. Read again and you get an empty string:
with open("notes/piranesi.txt") as f:
first = f.read()
second = f.read()
print(len(first) > 0, "then", len(second))
True then 0The same thing bites you if you f.read() and then try to loop the file: the
loop starts from the end and yields nothing. Read the file once, into a variable,
and work with the variable.
Using readlines "to be safe". for line in f and f.readlines() look
similar, but only the loop is memory-safe on a large file. There's no safety in
readlines; there's a hidden cost.
Forgetting the header row. If the first line of the file is column names, it
flows through your loop like any other line. Skip it with next(f) or
f.readline() before the loop, or check for it inside.
Practice
Try each of these before you read the solution under it.
- Loop over
reading-log.csvand print the title of every book you haven't finished. Those are the rows where the last field is empty, so the line ends with a comma before its newline. - Write
first_lines(path, n)that returns the firstnlines of a file as a list, stripped of their newlines, without reading the whole file. - Count how many books are finished and how many are still in progress, reading the file only once.
Solutions
1. Strip the newline, then check whether the line ends with a comma.
with open("reading-log.csv") as f:
next(f) # skip the header
for line in f:
if line.rstrip("\n").endswith(","):
print(line.split(",")[0])
Recursion
The Three-Body Problem2. Stop the loop once you have n lines. The file is never read past that
point.
def first_lines(path, n):
lines = []
with open(path) as f:
for line in f:
lines.append(line.rstrip("\n"))
if len(lines) == n:
break
return lines
for line in first_lines("reading-log.csv", 3):
print(line)
title,author,rating,finished
The Left Hand of Darkness,Ursula K. Le Guin,5,2025-11-02
Project Hail Mary,Andy Weir,4,2026-01-153. One pass, one counter each.
finished = 0
reading = 0
with open("reading-log.csv") as f:
next(f)
for line in f:
if line.rstrip("\n").endswith(","):
reading += 1
else:
finished += 1
print(f"{finished} finished, {reading} still reading")
8 finished, 2 still readingWhere this leaves you
You can read a file whole, line by line, or a piece at a time, and you know why the line-by-line loop is the one to reach for by default. You've also seen the first crack in splitting lines by hand, which Chapter 7 will deal with properly. Chapter 3 is the other direction: writing text back out to a file.