🏗️ Phase 3 · OOP 🟡 Intermediate MODULE 13

File Handling

⏱️ 40 min
📖 Theory + Code
🧩 5 Questions
🏗️ 1 Challenge
Phase 3 progress50%
🎯 What you'll learn: Reading and writing text files with open() and the with statement. All file modes (r, w, a, r+). Reading methods: read(), readline(), readlines(). CSV files with the csv module. JSON files with json. Working with file paths using pathlib.

Opening Files — The with Statement

Python uses open(filename, mode) to open a file. Always use the with statement (context manager) — it guarantees the file is properly closed even if an error occurs. Without with, you risk file corruption and resource leaks.

ModeNameBehaviour
"r"ReadOpen for reading. File must exist or FileNotFoundError.
"w"WriteCreate new (or truncate existing). Destroys existing content!
"a"AppendOpen for writing at end. Creates file if it doesn't exist.
"r+"Read+WriteOpen for both reading and writing. File must exist.
"x"Exclusive CreateCreate new file. Fails if it already exists.
"b"Binary suffixAdd to any mode: "rb", "wb" — for images, PDFs, etc.
file_basics.py
PYTHON
# ── WRITING a file ───────────────────────────
with open("notes.txt", "w", encoding="utf-8") as f:
    f.write("Line 1: Python is awesome!\n")
    f.write("Line 2: File handling is easy.\n")
    f.write("Line 3: Always use 'with'.\n")
# File is automatically closed here

# ── READING the whole file at once ───────────
with open("notes.txt", "r", encoding="utf-8") as f:
    content = f.read()
    print(content)

# ── READING line by line (memory-efficient) ──
with open("notes.txt", "r", encoding="utf-8") as f:
    for line in f:
        print(line.strip())   # strip removes the trailing \n

# ── READING into a list ───────────────────────
with open("notes.txt") as f:
    lines = f.readlines()     # list of strings, each with \n
    print(f"{len(lines)} lines read")

# ── APPENDING to existing file ───────────────
with open("notes.txt", "a", encoding="utf-8") as f:
    f.write("Line 4: Appended without overwriting!\n")

# ── WRITING multiple lines at once ───────────
data = ["Ali\n", "Sara\n", "Zara\n"]
with open("names.txt", "w") as f:
    f.writelines(data)
⚠️
Always specify encoding="utf-8"
Without encoding, Python uses the system default — which differs between Windows (cp1252), macOS (utf-8), and Linux (utf-8). This causes errors when your file contains Urdu, Arabic, emoji, or any non-ASCII character. Always write encoding="utf-8" explicitly. It's also required for deployment on servers.

CSV Files

CSV (Comma-Separated Values) is the most common format for tabular data — spreadsheets, database exports, data science datasets. Python's built-in csv module handles all the edge cases (quoted fields, commas inside values, different delimiters) that manual string splitting misses.

csv_files.py
PYTHON
import csv

# ── WRITING a CSV ─────────────────────────────
students = [
    ["Name",    "Age", "GPA",  "City"],       # header row
    ["Ali",     20,   3.85, "Lahore"],
    ["Sara",    22,   3.92, "Karachi"],
    ["Fatima",  21,   3.75, "Islamabad"],
]

with open("students.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerows(students)

# ── READING a CSV ─────────────────────────────
with open("students.csv", "r", encoding="utf-8") as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)

# ── DictReader — rows as dicts (headers as keys) ─
with open("students.csv", "r", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(f"{row['Name']}: GPA {row['GPA']}")
# Ali: GPA 3.85
# Sara: GPA 3.92

# ── DictWriter — write dicts to CSV ──────────
records = [
    {"product": "Book",      "price": 1500},
    {"product": "Keyboard",  "price": 3200},
]
with open("products.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["product", "price"])
    writer.writeheader()
    writer.writerows(records)
💡
Always use newline="" when writing CSV on Windows
On Windows, opening a file in text mode adds an extra \r carriage return character to each line. The csv module adds its own line endings, so you get double-newlines. Fix: always pass newline="" to open() when using the csv module.

JSON Files

JSON (JavaScript Object Notation) is the universal format for web APIs, configuration files, and data exchange. Python's json module converts between Python dicts/lists and JSON text with two key functions: json.dumps() (to string) and json.dump() (to file). json.loads() (from string) and json.load() (from file) go the other way.

json_files.py
PYTHON
import json

data = {
    "app"    : "BitWithBite",
    "version": "2.0",
    "students": [
        {"name": "Ali",  "gpa": 3.85},
        {"name": "Sara", "gpa": 3.92},
    ],
    "active": True
}

# ── Write to JSON file ────────────────────────
with open("data.json", "w", encoding="utf-8") as f:
    json.dump(data, f, indent=2, ensure_ascii=False)
# indent=2 makes it human-readable
# ensure_ascii=False preserves Urdu/Arabic characters

# ── Read from JSON file ───────────────────────
with open("data.json", "r", encoding="utf-8") as f:
    loaded = json.load(f)

print(loaded["app"])                   # BitWithBite
print(loaded["students"][0]["name"])  # Ali

# ── JSON string conversion ────────────────────
json_str = json.dumps(data, indent=2)   # dict → string
back     = json.loads(json_str)          # string → dict

# ── Useful pattern: update a JSON config ─────
def update_config(path, key, value):
    with open(path) as f: config = json.load(f)
    config[key] = value
    with open(path, "w") as f: json.dump(config, f, indent=2)

update_config("data.json", "version", "2.1")

pathlib — Modern Path Handling

Python 3.6+ includes pathlib, the modern object-oriented way to work with file system paths. It's far better than string concatenation for paths — it works identically on Windows, macOS, and Linux, and has a clean, fluent API.

pathlib_demo.py
PYTHON
from pathlib import Path

# Current directory
cwd = Path.cwd()
print(cwd)

# Build paths with / operator (works on all OS!)
data_dir = cwd / "data"
file_path= data_dir / "students.csv"

# Create directories
data_dir.mkdir(exist_ok=True)   # no error if already exists

# Path properties
p = Path("data/students.csv")
print(p.name)        # students.csv
print(p.stem)        # students
print(p.suffix)      # .csv
print(p.parent)      # data
print(p.exists())     # True/False
print(p.is_file())    # True if it's a file

# Read/write directly from Path objects
config = Path("config.txt")
config.write_text("debug=True\nport=8000\n", encoding="utf-8")
print(config.read_text(encoding="utf-8"))

# List all files matching a pattern
for csv_file in Path(".").glob("*.csv"):
    print(f"Found: {csv_file.name} ({csv_file.stat().st_size} bytes)")

# Recursive glob — find all Python files in any subdirectory
for py_file in Path(".").rglob("*.py"):
    print(py_file)
pathlib vs os.path — always prefer pathlib
Old code uses os.path.join("data", "file.csv") — error-prone on Windows vs Linux. New code uses Path("data") / "file.csv" — clean, cross-platform, and object-oriented. Pathlib objects can be passed directly to open(), json.load(), and most modern libraries.
🧩 Knowledge Check
5 questions — File Handling
1. Why should you always use the with statement when opening files?
2. Which file mode opens for writing and destroys existing content?
3. What does json.dumps(data) return?
4. How do you build a file path cross-platform with pathlib?
5. What does csv.DictReader return for each row?
🏗️
Challenge — Student Records Manager
CSV + JSON + pathlib all working together
Task: Build a student records manager that:

1. Loads students from a CSV file (students.csv) using DictReader — columns: name, age, gpa, city
2. Converts each row's gpa to float; groups students into {"honours": [], "pass": [], "fail": []} based on gpa thresholds (≥3.7 honours, ≥2.0 pass, else fail)
3. Saves the grouped report to a JSON file (report.json) with indent=2
4. Adds a new student via user input and appends them to the CSV file
5. Uses pathlib to check if students.csv exists before reading, and creates it with sample data if it doesn't
6. Prints a summary: total students, honours count, average GPA
💡 Show hints
  • Pathlib check: if not Path("students.csv").exists(): ... then write sample data
  • Append row: open with "a", use csv.writer, write one row
  • Average GPA: sum(float(r["gpa"]) for r in rows) / len(rows)
  • grouping: build dict, then .setdefault() or just if/elif
student_records.py — Sample Solution
PYTHON
import csv, json
from pathlib import Path

CSV_FILE  = Path("students.csv")
JSON_FILE = Path("report.json")

# ── Create sample CSV if missing ──────────────
if not CSV_FILE.exists():
    sample = [
        ["name",    "age", "gpa", "city"],
        ["Ali",     20,   3.85, "Lahore"],
        ["Sara",    22,   3.92, "Karachi"],
        ["Zara",    21,   2.50, "Multan"],
        ["Omar",    23,   1.80, "Quetta"],
    ]
    with open(CSV_FILE, "w", newline="") as f:
        csv.writer(f).writerows(sample)
    print("✅ Created sample students.csv")

# ── Load and group ────────────────────────────
with open(CSV_FILE, encoding="utf-8") as f:
    rows = list(csv.DictReader(f))

groups = {"honours": [], "pass": [], "fail": []}
for r in rows:
    g = float(r["gpa"])
    if   g >= 3.7: groups["honours"].append(r["name"])
    elif g >= 2.0: groups["pass"].append(r["name"])
    else:           groups["fail"].append(r["name"])

# ── Save JSON report ──────────────────────────
with open(JSON_FILE, "w") as f:
    json.dump(groups, f, indent=2)
print(f"✅ Report saved to {JSON_FILE}")

# ── Add a new student ─────────────────────────
print("\n  Add a new student:")
new = {
    "name": input("  Name : ").strip().title(),
    "age" : input("  Age  : ").strip(),
    "gpa" : input("  GPA  : ").strip(),
    "city": input("  City : ").strip().title(),
}
with open(CSV_FILE, "a", newline="") as f:
    csv.DictWriter(f, fieldnames=["name","age","gpa","city"]).writerow(new)
print(f"  ✅ {new['name']} added!")

# ── Summary ───────────────────────────────────
avg_gpa = sum(float(r["gpa"]) for r in rows) / len(rows)
print(f"\n  Students: {len(rows)}  Honours: {len(groups['honours'])}  Avg GPA: {avg_gpa:.2f}")
🎉
Lesson 13 Complete!
File handling mastered! Next — Exception Handling to make programs bulletproof.
← Course Home
Phase 3 · OOPLesson 13 of 6