File Handling (read, write, append, context manager, pathlib) - Python Tutorial for Beginners #15
Video: File Handling (read, write, append, context manager, pathlib) - Python Tutorial for Beginners #15 by Taught by Celeste AI - AI Coding Coach
Python File Handling: open, read, write, with
with open(path, mode) as f:is the canonical pattern. Modes:"r"read,"w"write (truncate),"a"append,"b"binary,"+"read/write. Thewithblock guarantees the file closes even on exception.
Reading and writing files is one of the first "real-world" things you'll do with Python. The pattern is small but worth getting right.
The basic pattern: with open
with open("hello.txt", "w") as f:
f.write("Hello, World!\n")
f.write("Python is awesome!\n")
with open(path, mode) as f: opens the file. The with block guarantees it gets closed when the block ends — even if an exception is raised.
Three modes you'll use 95% of the time:
"r"— read (default if mode omitted). File must exist."w"— write. Truncates existing content."a"— append. Creates if missing; writes to end.
Reading: three ways
# Read everything as one string
with open("hello.txt", "r") as f:
content = f.read()
print(content)
# Read all lines into a list
with open("hello.txt", "r") as f:
lines = f.readlines() # list of strings, with newlines
# Iterate line by line — most memory-efficient
with open("hello.txt", "r") as f:
for line in f:
print(line.strip()) # strip removes the trailing \n
For huge files, iterate line-by-line. read() and readlines() load the whole file into memory.
line.strip() is needed because each line includes its trailing \n. Or use line.rstrip("\n") to keep other whitespace.
Writing
with open("output.txt", "w") as f:
f.write("Line 1\n")
f.write("Line 2\n")
# Multiple lines at once
with open("output.txt", "w") as f:
f.writelines(["Line 1\n", "Line 2\n", "Line 3\n"])
f.write(s) writes one string. No automatic newline — you have to add \n.
f.writelines(iterable) writes each string from the iterable. Despite the name, it doesn't add newlines either.
Append vs write
with open("log.txt", "w") as f:
f.write("First entry\n") # creates file, writes
with open("log.txt", "w") as f:
f.write("Second entry\n") # TRUNCATES, file now has only second entry
with open("log.txt", "a") as f:
f.write("Third entry\n") # appends — preserves first
"w" always starts fresh. "a" adds to the end. For logs, always "a".
A log manager
logs = [
("INFO", "Application started"),
("WARNING", "Low memory detected"),
("ERROR", "Failed to connect"),
]
# Write
with open("app.log", "w") as f:
for level, message in logs:
f.write(f"[{level}] {message}\n")
# Filter ERRORs
with open("app.log", "r") as f:
errors = [line.strip() for line in f if "ERROR" in line]
# Count by level
counts = {}
with open("app.log", "r") as f:
for line in f:
level = line.split("]")[0].strip("[")
counts[level] = counts.get(level, 0) + 1
Three patterns in one file: structured write, filtered read, aggregate count. The with block in each opens just for that operation.
Why with matters
Without with, you'd have to remember to close the file:
f = open("file.txt", "r")
content = f.read()
f.close() # easy to forget — and skipped on exception
If f.read() raises, close() never runs. The file stays open until garbage collection — could be milliseconds, could be much longer. On Windows, an unclosed file blocks deletion. On any OS, leaking file handles will eventually exhaust the OS limit.
with is a context manager — it ensures cleanup. Use it for every file operation.
Modes in detail
| Mode | Description |
|---|---|
"r" |
read text (default) |
"w" |
write text (truncate) |
"a" |
append text |
"r+" |
read + write (must exist) |
"w+" |
write + read (truncate) |
"a+" |
append + read |
"rb", "wb", "ab" |
same, but binary mode |
"x" |
exclusive create — fails if file exists |
For binary files (images, PDFs, etc.) always use b:
with open("image.png", "rb") as f:
data = f.read() # bytes, not str
Text mode handles encoding (default UTF-8 in Python 3). Binary mode gives you raw bytes.
Encoding
with open("notes.txt", "r", encoding="utf-8") as f:
text = f.read()
Always specify encoding="utf-8". Default depends on platform — on Windows it's often cp1252, which silently mangles non-ASCII. Explicit is safer.
For latin1, shift-jis, etc., specify the appropriate name.
File position: seek and tell
with open("file.txt", "r") as f:
print(f.tell()) # 0 — start
f.read(5)
print(f.tell()) # 5
f.seek(0) # back to start
f.read(10)
f.seek(0, 2) # seek to end (offset 0 from end-of-file marker 2)
Rarely needed for text. Useful for binary protocols where you need to jump around.
Pathlib: the modern way
from pathlib import Path
p = Path("hello.txt")
p.write_text("Hello!\n")
content = p.read_text()
p.write_bytes(b"\x00\x01")
data = p.read_bytes()
# File operations
p.exists()
p.is_file()
p.unlink() # delete
pathlib.Path is more ergonomic for most file work. read_text / write_text open, read/write, close — no with needed for one-off reads.
For line-by-line reads of huge files, still use with open(...).
Reading from URLs vs files
# Local
with open("data.json") as f:
data = json.load(f)
# Remote
import requests
r = requests.get("https://api.example.com/data.json")
data = r.json()
Pattern is the same. We cover JSON in lesson 18 and HTTP requests in lesson 32.
CSV files
import csv
with open("data.csv", "r") as f:
reader = csv.reader(f)
for row in reader:
print(row) # list of strings
# DictReader for headers
with open("data.csv", "r") as f:
reader = csv.DictReader(f)
for row in reader:
print(row["name"], row["age"])
Use the csv module — don't try to split on commas yourself (commas inside quoted fields will trip you up). Even better: pandas.read_csv() for analysis.
Common stumbles
Forgetting with. File leaks. Always use with.
Wrong mode. "w" overwrites — use "a" if you mean to append.
Forgetting \n. f.write("line") — no newline added. The next write runs on the same line.
Iteration on closed file. Once the with block ends, f is closed. Don't store and reuse outside.
Non-ASCII corruption. Always pass encoding="utf-8" explicitly.
Reading a huge file with .read(). Loads into memory. Iterate line-by-line for big files.
os.path.join everywhere. Use pathlib.Path — cleaner, cross-platform.
Mixing binary and text mode writes. f.write("string") in "wb" mode → TypeError. Bytes mode wants bytes: f.write(b"...").
What's next
Lesson 16: exception handling. try, except, else, finally, raise, custom exception classes.
Recap
with open(path, mode, encoding="utf-8") as f: for guaranteed close. Modes: "r" "w" "a" (text) and "rb" "wb" "ab" (binary). f.read() returns whole file; for line in f: iterates line by line (memory efficient). f.write(s) writes one string (no automatic newline). pathlib.Path for path manipulation and one-shot reads. Always specify encoding for text files.
Next lesson: exception handling.