Most useful programs read data from files. This lesson covers the three ways to read a text file in Python — and when to use each.

Set up a test file

Create a file called notes.txt in your project folder with a few lines:

Python is fun
Lists are powerful
Functions keep code clean

We’ll read it back in three different ways.

Read it all at once — .read()

with open("notes.txt") as f:
    content: str = f.read()

print(content)

f.read() returns the entire file as one string. Use this when:

  • The file is small (a config, a short text file)
  • You actually need the whole thing in memory

For huge files (a 10 GB log), this would crash your program. Don’t.

Read line by line — looping over the file

with open("notes.txt") as f:
    for line in f:
        print(line.rstrip())
Python is fun
Lists are powerful
Functions keep code clean

A file is its own iterable — Python yields one line at a time. This is memory-efficient and works for files of any size.

line.rstrip() strips the trailing newline. Without it, each print would add its own newline on top of the file’s, giving you blank lines between.

Read all lines into a list — .readlines()

with open("notes.txt") as f:
    lines: list[str] = f.readlines()

print(lines)
# ['Python is fun\n', 'Lists are powerful\n', 'Functions keep code clean\n']

.readlines() returns a list of every line. Each line keeps its trailing \n.

This is rarely the best option:

  • For loops, just iterate the file directly.
  • If you need a list of stripped lines, use a comprehension: [line.rstrip() for line in f].

Always use with

You may see older code that opens a file without a with:

f = open("notes.txt")
content = f.read()
f.close()

This works, but if f.read() raises an exception, f.close() never runs and the file stays open. Always use with — it closes the file for you, even on failure.

File modes

open(path) defaults to read-text mode ("r"). Other modes:

ModeMeaning
"r"Read text (default)
"w"Write text (overwrites!)
"a"Append text
"r+"Read and write
"rb"Read binary
"wb"Write binary

We’ll cover writing in the next lesson.

Handling missing files

If the file doesn’t exist, open() raises FileNotFoundError:

try:
    with open("missing.txt") as f:
        content = f.read()
except FileNotFoundError:
    print("File not found")

You can also check first with pathlib:

from pathlib import Path

if Path("notes.txt").exists():
    with open("notes.txt") as f:
        content = f.read()

In most cases, the try-and-handle style is preferred — the file might be deleted between the check and the open.

Encodings — be explicit

Text files are sequences of bytes, and how those bytes turn into characters depends on the encoding. The modern, almost-universal default is UTF-8.

Python uses your system’s default encoding unless you specify one — which can produce different results on different machines (especially on Windows). Always pass encoding="utf-8" for portability:

with open("notes.txt", encoding="utf-8") as f:
    content = f.read()

This avoids subtle bugs when your file has emoji, accents, or non-Latin characters.

A complete example — count words in a file

from pathlib import Path


def count_words(path: Path) -> int:
    total: int = 0
    with open(path, encoding="utf-8") as f:
        for line in f:
            total += len(line.split())
    return total


print(count_words(Path("notes.txt")))

We loop line by line, split each line into words, and add up. No matter how big the file is, only one line is in memory at a time.

What’s next

Reading is half the picture. Next, writing files.

Toggle theme (T)