W3docs

Python Read Files – Complete Guide

Learn every way to read files in Python: read(), readline(), readlines(), line iteration, binary mode, encoding, pathlib, and large-file patterns.

Reading files is one of the most common tasks in Python — whether you are loading configuration, processing logs, importing CSV data, or reading binary assets. This chapter covers every method Python provides for reading file content: from the simplest read() call to memory-efficient line iteration, binary mode, encoding handling, file position control, and the modern pathlib API.

Opening a File for Reading

Every file operation starts with the built-in open() function. To open a file in read mode, use the mode string "r" (which is the default when no mode is given):

file_object = open("notes.txt", "r", encoding="utf-8")
  • "r" — open for reading; raises FileNotFoundError if the file does not exist.
  • encoding="utf-8" — always specify the character encoding for text files so your code works identically on Windows, macOS, and Linux.

Always Use a with Block

The safest way to open a file is inside a with statement. Python automatically closes the file when the block exits — even if an exception occurs — which prevents resource leaks and ensures any buffered data is flushed.

with open("notes.txt", "r", encoding="utf-8") as f:
    contents = f.read()
# File is automatically closed here

Calling open() outside a with block and forgetting file.close() is a frequent source of "too many open files" errors in long-running scripts.

Reading the Entire File with read()

read() returns the complete contents of the file as a single string.

Read an entire file at once

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

print(contents)

Use read() when:

  • The file is small enough to fit comfortably in memory.
  • You need the full text as one string (e.g., for parsing or searching).

For files that might be large (logs, data dumps), prefer the line-by-line approaches described below.

Read a Fixed Number of Characters

Pass an integer n to read(n) to read at most n characters from the current position. Subsequent calls to read(n) continue from where the last read left off.

Read the first 50 characters

with open("notes.txt", "r", encoding="utf-8") as f:
    first_chunk = f.read(50)
    second_chunk = f.read(50)

print(repr(first_chunk))
print(repr(second_chunk))

This chunked approach is useful when you want to preview a file or process it in fixed-size pieces without loading everything into memory.

Reading One Line at a Time with readline()

readline() reads one line from the file, including the trailing newline character \n. It returns an empty string "" when the end of the file is reached, which makes it easy to loop until EOF.

Read a file line by line with readline()

with open("notes.txt", "r", encoding="utf-8") as f:
    line = f.readline()
    while line:
        print(line, end="")   # line already contains '\n'
        line = f.readline()

The end="" argument in print() prevents a double newline from appearing (one from the file line and one from print's default).

readline() is useful when you need to process a header line differently from the rest, or when you want to stop reading partway through the file based on a condition.

Iterating Over Lines (Most Pythonic)

Iterating directly over a file object is the most idiomatic and memory-efficient way to process a text file line by line. Python reads one line at a time without loading the whole file into memory.

Iterate over lines with a for loop

with open("notes.txt", "r", encoding="utf-8") as f:
    for line in f:
        print(line, end="")

This pattern is preferred over read() + split("\n") and over calling readline() in a while loop, because it is shorter and handles all edge cases (including files that do not end with a newline) correctly.

Search for a pattern while iterating

with open("server.log", "r", encoding="utf-8") as f:
    for line in f:
        if "ERROR" in line:
            print(line, end="")

Reading All Lines into a List with readlines()

readlines() returns a list where each element is one line of the file (with the newline character included). This loads the entire file into memory.

Read all lines into a list

with open("notes.txt", "r", encoding="utf-8") as f:
    lines = f.readlines()

print(lines[0])        # first line
print(lines[-1])       # last line
print(len(lines))      # total number of lines

Use readlines() when you need random access to specific lines by index. For sequential processing from top to bottom, the for line in f pattern is more memory-efficient.

Strip Newline Characters

Lines returned by readline(), readlines(), and the for loop iteration all include the trailing \n. Use .strip() or .rstrip("\n") to remove it:

with open("notes.txt", "r", encoding="utf-8") as f:
    lines = [line.rstrip("\n") for line in f]

print(lines)  # ['Line one', 'Line two', 'Line three']

Choosing the Right Reading Method

MethodReturnsLoads whole file?Best for
f.read()strYesSmall files, full-text parsing
f.read(n)strNo (chunked)Fixed-size streaming
f.readline()strNoConditional line-by-line stops
for line in fstr (one per iteration)NoSequential line processing
f.readlines()list[str]YesRandom index access to lines

File Position: tell() and seek()

Every open file object maintains an internal position pointer that advances as you read. Two methods let you inspect and control it:

  • tell() — returns the current byte offset from the start of the file.
  • seek(offset, whence=0) — moves the pointer. With whence=0 (default) the offset is from the start; whence=1 is from the current position; whence=2 is from the end.

Read a file twice using seek(0)

with open("notes.txt", "r", encoding="utf-8") as f:
    first_pass = f.read()
    print(f"Position after first read: {f.tell()}")

    f.seek(0)   # rewind to the beginning
    second_pass = f.read()

print(first_pass == second_pass)  # True

seek() is particularly useful in "r+" (read-and-write) mode, where you might read a section of a file and then overwrite a specific part in the same open() call.

Handling Errors When Reading

A well-written script always anticipates the ways a file read can fail.

Handle common read errors

try:
    with open("data.txt", "r", encoding="utf-8") as f:
        content = f.read()
except FileNotFoundError:
    print("Error: the file does not exist.")
except PermissionError:
    print("Error: you do not have permission to read this file.")
except UnicodeDecodeError:
    print("Error: the file contains bytes that are not valid UTF-8.")
except OSError as e:
    print(f"OS error: {e}")

Common exceptions you will encounter:

ExceptionWhen it occurs
FileNotFoundErrorThe path does not point to an existing file
PermissionErrorThe process lacks read permission
IsADirectoryErrorThe path points to a directory, not a file
UnicodeDecodeErrorFile bytes do not match the specified encoding

See Python Try Except for a full guide to exception handling.

Character Encoding

When Python opens a file in text mode, it must know how to convert raw bytes into characters. Always pass encoding= explicitly rather than relying on the platform default, which differs between Windows (cp1252) and most Unix systems (utf-8).

Common encoding values:

EncodingUse when
"utf-8"Modern files, web content, most Python projects
"utf-8-sig"UTF-8 files created by Windows tools that prepend a BOM
"latin-1"Legacy Western-European files
"cp1252"Windows ANSI text files

Detect or ignore encoding problems:

If you are unsure of a file's encoding, you can tell Python to replace undecodable bytes with a placeholder rather than raising an error:

with open("mystery.txt", "r", encoding="utf-8", errors="replace") as f:
    content = f.read()

Other values for errors include "ignore" (silently skip bad bytes) and "strict" (default — raise UnicodeDecodeError).

Reading Binary Files

Open a file in binary mode by adding "b" to the mode string ("rb"). Binary mode returns raw bytes objects instead of strings, which is correct for images, audio, compressed archives, executables, and any non-text data.

Read a binary file

with open("photo.jpg", "rb") as f:
    data = f.read()

print(type(data))   # <class 'bytes'>
print(len(data))    # size in bytes

Copy a binary file

with open("photo.jpg", "rb") as src:
    data = src.read()

with open("photo_backup.jpg", "wb") as dst:
    dst.write(data)

Do not specify encoding in binary mode — Python raises ValueError if you try.

Reading Large Files Efficiently

Loading a multi-gigabyte file with read() can exhaust system memory. The solutions are:

Line-by-line iteration (text files)

with open("huge_log.txt", "r", encoding="utf-8") as f:
    for line in f:
        process(line)   # only one line in memory at a time

Fixed-size chunks (binary files)

CHUNK_SIZE = 65536  # 64 KB

with open("large_file.bin", "rb") as f:
    while True:
        chunk = f.read(CHUNK_SIZE)
        if not chunk:
            break
        process(chunk)

Both patterns keep memory usage constant regardless of file size.

Reading Files with pathlib

Python 3.4 introduced pathlib.Path, which provides an object-oriented interface to file system paths. For simple one-shot reads and writes, Path objects are more concise than open().

Read text with Path.read_text()

from pathlib import Path

content = Path("notes.txt").read_text(encoding="utf-8")
print(content)

Read bytes with Path.read_bytes()

from pathlib import Path

data = Path("photo.jpg").read_bytes()
print(len(data))  # file size in bytes

read_text() and read_bytes() open the file, read its entire content, and close it in a single call. Use them for small files when you just need the content. Use open() with a with block when you need line-by-line iteration, chunked reading, seek(), or any other fine-grained control.

Check That a File Exists Before Reading

from pathlib import Path

p = Path("data.txt")
if p.exists() and p.is_file():
    content = p.read_text(encoding="utf-8")
else:
    print("File not found.")

Note: p.exists() can return a stale result in multi-threaded code. In those cases it is safer to attempt the read and catch FileNotFoundError.

Practical Example: Reading a Simple Config File

Many scripts read a plain-text configuration file that stores one key=value pair per line. Here is a complete, working example:

Parse a key=value config file

from pathlib import Path

def load_config(path):
    config = {}
    with open(path, "r", encoding="utf-8") as f:
        for line in f:
            line = line.strip()
            if not line or line.startswith("#"):
                continue          # skip blank lines and comments
            key, _, value = line.partition("=")
            config[key.strip()] = value.strip()
    return config

# Example config.txt contents:
# host = localhost
# port = 8080
# debug = true

config = load_config("config.txt")
# config == {'host': 'localhost', 'port': '8080', 'debug': 'true'}

str.partition("=") splits on the first = only, so values that contain = (such as Base64 strings) are handled correctly.

Was this page helpful?