Opening and Closing Files
The open() function returns a file object and accepts a mode string that controls how the stream behaves. Common text modes are r, w, a, r+, w+, and a+. Binary modes append b, e.g., rb, wb. Always close a file when finished, preferably using a with statement.
with open("log.txt", "r", encoding="utf-8") as handle:
data = handle.read()
Read Modes (r, rb)
Text Mode
with open("notes.txt", "r", encoding="utf-8") as handle:
body = handle.read()
print(body)
r mode needs a encoding matching the file’s actual byte representation. Without arguments, read() consumes the entire file.
Binary Mode
with open("notes.txt", "rb") as handle:
raw = handle.read()
print(raw) # bytes object, e.g., b'hello'
Binary mode is essential for non-text formats such as images, videos, or MP3 files. No encoding is allowed.
Path Types
- Absolute path – starts from the root directory, e.g.,
C:\Users\docs\data.csv. - Relative path – relative to the current working directory. Use
..for a parent folder. Relative paths make projects portable.
Incremental Reading
read() loads everything into memory. Larger files require a streamed approach.
with open("large_log.txt", "r", encoding="utf-8") as handle:
chunk = handle.read(5) # first 5 characters
second_chunk = handle.read(5)
print(chunk, second_chunk)
Positions advance automatically. Binary mode’s read(n) reads n bytes.
with open("large_log.txt", "rb") as handle:
block = handle.read(4)
print(block) # b'\x48\x65\x6c\x6c'
readline() fetches a single line, including the trailing newline. Call strip() to remove it.
with open("lyrics.txt", "r", encoding="utf-8") as handle:
first_line = handle.readline()
second_line = handle.readline()
print(first_line.strip(), second_line.strip())
readlines() builds a list of all lines—memory intensive.
Best practice for line-by-line processing:
with open("lyrics.txt", "r", encoding="utf-8") as handle:
for line in handle:
print(line.strip())
This idiom never loads the whole file into memory.
Write Modes (w, wb)
Write mode creates a new file or truncates a existing one before writing.
with open("output.txt", "w", encoding="utf-8") as handle:
handle.write("First line\n")
handle.write("Second line\n")
Attempting to read in w mode raises io.UnsupportedOperation.
Binary write requires encoded bytes:
with open("output.bin", "wb") as handle:
handle.write("data payload".encode("utf-8"))
Append Modes (a, ab)
Append adds content at the end without removing existing data.
with open("journal.txt", "a", encoding="utf-8") as handle:
handle.write("\nNew entry")
Read‑Write Modes (r+, w+, a+)
r+ opens for reading and writing. Read first, then write; otherwise the cursor position may overwrite initial content.
with open("data.txt", "r+", encoding="utf-8") as handle:
existing = handle.read()
handle.write(" appended text")
print(existing)
w+ truncates immediately and read returns empty data. Rarely useful.
a+ allows reading and appending; reading initially returns nothing because the pointer is at the end.
Cursor Control
seek(offset, whence) moves the internal pointer. whence can be:
0– start (default)1– current position2– end
For UTF‑8 text, offsets must align to character boundaries (multiples of 3 for many Chinese characters).
with open("entries.txt", "r+", encoding="utf-8") as handle:
handle.seek(0) # move to start
text = handle.read()
handle.seek(0, 2) # move to end
handle.write("\nFooter")
handle.seek(0) # move back to start
print(handle.read())
tell() reports the current byte offset.
with open("entries.txt", "r+", encoding="utf-8") as handle:
handle.write("abc")
print(handle.tell()) # 3
truncate(size=None) resizes the file. Without size, it cuts at the current pointer. With size, it keeps the first size bytes.
with open("draft.txt", "r+", encoding="utf-8") as handle:
handle.seek(10)
handle.truncate() # keep only first 10 bytes from start
Important caveat: After any read in r+ mode, subsequent writes append at the end regardless of seek() calls. To truncate reliably, move the cursor first, truncate, then write only when necessary.
Safe In‑Place File Editing
Direct mid‑file modifications risk data loss. The standard pattern reads, modifies, and swaps files.
import os
source = "original.txt"
temp = "original.tmp"
with open(source, "r", encoding="utf-8") as f_in, \
open(temp, "w", encoding="utf-8") as f_out:
for line in f_in:
updated = line.replace("old_term", "new_term")
f_out.write(updated)
os.remove(source)
os.rename(temp, source)
This streaming approach handles files of any size safely and uses with to ensure clean closure.