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; raisesFileNotFoundErrorif 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 hereCalling 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 linesUse 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
| Method | Returns | Loads whole file? | Best for |
|---|---|---|---|
f.read() | str | Yes | Small files, full-text parsing |
f.read(n) | str | No (chunked) | Fixed-size streaming |
f.readline() | str | No | Conditional line-by-line stops |
for line in f | str (one per iteration) | No | Sequential line processing |
f.readlines() | list[str] | Yes | Random 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. Withwhence=0(default) the offset is from the start;whence=1is from the current position;whence=2is 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) # Trueseek() 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:
| Exception | When it occurs |
|---|---|
FileNotFoundError | The path does not point to an existing file |
PermissionError | The process lacks read permission |
IsADirectoryError | The path points to a directory, not a file |
UnicodeDecodeError | File 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:
| Encoding | Use 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 bytesCopy 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 timeFixed-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 bytesread_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.
Related Chapters
- Python File Handling — opening modes,
withstatement,seek(), binary files, pathlib overview - Python Write / Create Files — writing, creating, and appending to files
- Python Delete Files — deleting files and directories safely
- Python Try Except — handling exceptions in Python
- Python CSV — reading and writing CSV files with the
csvmodule