Modules Data And Errors
File Handling
file = open("notes.txt", "r") # "r" = read mode
Jr Codex Python Notes
Level: Intermediate Prerequisites: Chapter 1 Time to complete: ~20 minutes
Table of Contents
- Opening a File
- The
withStatement — Why It Matters - Reading Files
- Writing Files
- File Modes Reference
- Working with Paths
- Summary & Next Steps
1. Opening a File
file = open("notes.txt", "r") # "r" = read mode
content = file.read()
print(content)
file.close() # must remember to close manually!This works, but has a real problem: if an error occurs between open() and close(), the file never gets closed — leaking a file handle. Section 2 shows the fix.
2. The with Statement — Why It Matters
The with statement is a context manager — it guarantees the file is closed automatically, even if an exception occurs inside the block. This is the standard, idiomatic way to work with files in Python.
with open("notes.txt", "r") as file:
content = file.read()
print(content)
# file is automatically closed here, even if the code above raised an errorWithout `with` With `with`
───────────────────────────── ─────────────────────────────
file = open(...) with open(...) as file:
# ... risky code ... # ... risky code ...
file.close() ← may never run! # ALWAYS closed on exit, error or not
───────────────────────────── ─────────────────────────────
We revisit exactly how with works under the hood (the context manager protocol) in Module 5 — for now, treat it as the required pattern for file I/O.
3. Reading Files
# Read the entire file as one string
with open("notes.txt", "r") as file:
content = file.read()
# Read all lines into a list of strings (each includes its trailing \n)
with open("notes.txt", "r") as file:
lines = file.readlines()
print(lines) # ['first line\n', 'second line\n', ...]
# Read one line at a time
with open("notes.txt", "r") as file:
first_line = file.readline()
# Memory-efficient: iterate line by line WITHOUT loading the whole file
with open("notes.txt", "r") as file:
for line in file:
print(line.strip()) # .strip() removes the trailing newline| Method | Returns | Best For |
|---|---|---|
.read() | Entire file as one string | Small files |
.readlines() | List of lines (each with \n) | When you need all lines in memory as a list |
.readline() | One line at a time | Manual, step-by-step reading |
for line in file: | Iterates lazily | Large files — never loads the whole file into memory |
4. Writing Files
# "w" mode — creates the file if missing, OVERWRITES if it exists
with open("output.txt", "w") as file:
file.write("Hello, World!\n")
file.write("Second line\n")
# "a" mode — appends to the end, never overwrites existing content
with open("output.txt", "a") as file:
file.write("This gets added at the end\n")
# writelines() — write a list of strings (does NOT add \n automatically!)
lines = ["line one\n", "line two\n", "line three\n"]
with open("output.txt", "w") as file:
file.writelines(lines)Common pitfall: "w" mode silently erases the existing file content before writing. Double-check you want "w" (overwrite) vs "a" (append) before running.
with open("important_data.txt", "w") as file: # ⚠️ this WIPES the file first
file.write("oops, everything else is gone now")5. File Modes Reference
| Mode | Meaning |
|---|---|
"r" | Read (default) — errors if file doesn't exist |
"w" | Write — creates file, overwrites if it exists |
"a" | Append — creates file if missing, adds to the end otherwise |
"x" | Exclusive create — errors if the file already exists |
"r+" | Read and write, file must exist |
"rb", "wb" | Binary mode (add b) — for non-text files: images, PDFs, etc. |
# Reading a binary file (e.g. an image)
with open("photo.jpg", "rb") as file:
data = file.read()
print(type(data)) # <class 'bytes'>, not str6. Working with Paths
Hardcoded path separators (\ on Windows, / on macOS/Linux) break across operating systems. pathlib (Python 3.4+) is the modern, cross-platform solution.
from pathlib import Path
data_dir = Path("data")
file_path = data_dir / "notes.txt" # `/` here is overloaded to JOIN paths, not divide!
print(file_path) # data/notes.txt (or data\notes.txt on Windows)
print(file_path.exists()) # True/False
print(file_path.name) # "notes.txt"
print(file_path.suffix) # ".txt"
print(file_path.parent) # "data"
# Reading/writing directly through Path — no need for open() for simple cases
if file_path.exists():
content = file_path.read_text()
file_path.write_text("New content")
# Listing files in a directory
for file in data_dir.glob("*.txt"):
print(file)Old Style (os.path) | Modern Style (pathlib) |
|---|---|
os.path.join("data", "notes.txt") | Path("data") / "notes.txt" |
os.path.exists(path) | path.exists() |
os.path.basename(path) | path.name |
Recommendation: use pathlib.Path for all new code — it's more readable and handles cross-platform path differences for you.
7. Summary & Next Steps
Key Takeaways
- Always use
with open(...) as file:— it guarantees the file closes properly, even if an error occurs. .read()loads the whole file; iterating withfor line in file:is the memory-efficient choice for large files."w"mode overwrites existing content;"a"mode appends — mixing these up is a common, costly mistake.- Prefer
pathlib.Pathover manual string concatenation for file paths — it's cross-platform by design.
Concept Check
- Why does
with open(...) as f:prevent resource leaks compared to manualopen()/close()? - What's the difference between
"w"and"a"mode, and what happens if you use the wrong one on an existing file? - Why is
Path("data") / "notes.txt"preferred over"data" + "/" + "notes.txt"?
Next Chapter
→ Chapter 3: Working with CSV & JSON
Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index