Python

Modules Data And Errors

File Handling

file = open("notes.txt", "r") # "r" = read mode

JrCodex·5 min read

Jr Codex Python Notes

Level: Intermediate Prerequisites: Chapter 1 Time to complete: ~20 minutes


Table of Contents

  1. Opening a File
  2. The with Statement — Why It Matters
  3. Reading Files
  4. Writing Files
  5. File Modes Reference
  6. Working with Paths
  7. 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 error
Without `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
MethodReturnsBest For
.read()Entire file as one stringSmall files
.readlines()List of lines (each with \n)When you need all lines in memory as a list
.readline()One line at a timeManual, step-by-step reading
for line in file:Iterates lazilyLarge 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

ModeMeaning
"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 str

6. 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 with for 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.Path over manual string concatenation for file paths — it's cross-platform by design.

Concept Check

  1. Why does with open(...) as f: prevent resource leaks compared to manual open()/close()?
  2. What's the difference between "w" and "a" mode, and what happens if you use the wrong one on an existing file?
  3. 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