Skip to content

File Handling

Read and write files on disk. The basic recipe is the same across languages: open → read or write → close.

The open() function

file = open("data.txt", "r")    # 'r' = read mode (default)
content = file.read()
file.close()

But the right way is to use with — it automatically closes the file, even on errors:

with open("data.txt", "r") as file:
    content = file.read()
# file is closed here automatically

Always use with. From now on, every example will.

File modes

Mode Meaning
"r" Read (default). File must exist.
"w" Write — overwrites existing content. Creates if missing.
"a" Append to end. Creates if missing.
"x" Create new file. Fails if exists.
"r+" Read + write.
"rb", "wb" Binary mode (images, PDFs, etc.)

Writing to a file

# Write a few lines
with open("greetings.txt", "w") as f:
    f.write("Hello, World!\n")
    f.write("Welcome to Python.\n")
    f.write("This is line 3.\n")

# Verify by reading back
with open("greetings.txt", "r") as f:
    print(f.read())

Note: in the browser (Pyodide) runner, the file lives in a virtual filesystem inside your tab — it disappears when you reload.

Reading from a file

Three common ways:

# 1. Read everything as one string
with open("greetings.txt", "r") as f:
    text = f.read()
print(repr(text))

# 2. Read all lines into a list
with open("greetings.txt", "r") as f:
    lines = f.readlines()
print(lines)

# 3. Read line by line — memory-efficient for big files
print("--- line-by-line ---")
with open("greetings.txt", "r") as f:
    for line in f:
        print(line.rstrip())     # rstrip removes the trailing \n

Appending — add to the end

# Append doesn't overwrite — adds to the existing file
with open("greetings.txt", "a") as f:
    f.write("Extra line 1\n")
    f.write("Extra line 2\n")

# Read back
with open("greetings.txt", "r") as f:
    print(f.read())

write vs writelines

lines = ["one\n", "two\n", "three\n"]

# writelines doesn't add newlines for you
with open("nums.txt", "w") as f:
    f.writelines(lines)

with open("nums.txt", "r") as f:
    print(f.read())

Working with CSV files

CSV = Comma-Separated Values. Use the csv module — handles edge cases (quoted commas, etc.).

import csv

# Write
rows = [
    ["name", "age", "city"],
    ["Alice", 25, "Mumbai"],
    ["Bob",   30, "Delhi"],
    ["Carol", 22, "Bangalore"],
]

with open("people.csv", "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerows(rows)

# Read
with open("people.csv", "r") as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)

Read CSV into a list of dicts with DictReader:

import csv

with open("people.csv", "r") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row)
        print(f"  Name: {row['name']}, City: {row['city']}")

Working with JSON files

import json

data = {
    "name": "Alice",
    "age": 25,
    "skills": ["Python", "ML", "FastAPI"],
    "active": True,
}

# Write
with open("profile.json", "w") as f:
    json.dump(data, f, indent=2)

# Read
with open("profile.json", "r") as f:
    loaded = json.load(f)

print(loaded)
print(loaded["skills"])

Checking if a file exists

from pathlib import Path

p = Path("greetings.txt")
print(p.exists())            # True
print(p.is_file())           # True
print(p.is_dir())            # False
print(p.stat().st_size)      # size in bytes

os.path is the older equivalent:

import os

print(os.path.exists("greetings.txt"))
print(os.path.getsize("greetings.txt"))

Delete or rename

import os

# Rename
os.rename("greetings.txt", "old-greetings.txt")
print(os.listdir("."))

# Delete
os.remove("old-greetings.txt")
print(os.listdir("."))

Or with pathlib:

from pathlib import Path

# Re-create for the demo
Path("temp.txt").write_text("hello")

# Rename
Path("temp.txt").rename("temp2.txt")

# Delete
Path("temp2.txt").unlink()

print("Done")

Working with paths properly (pathlib)

from pathlib import Path

# Build paths cross-platform
data_dir = Path("data")
data_dir.mkdir(exist_ok=True)
file_path = data_dir / "report.txt"

# Write directly
file_path.write_text("This is a report.\nLine 2.\n")

# Read directly
print(file_path.read_text())

# Parts
print("name:  ", file_path.name)
print("stem:  ", file_path.stem)
print("suffix:", file_path.suffix)
print("parent:", file_path.parent)

Binary files (images, PDFs)

# Write some bytes
data = bytes([0, 1, 2, 3, 4, 5, 255])
with open("bin.dat", "wb") as f:
    f.write(data)

# Read them back
with open("bin.dat", "rb") as f:
    content = f.read()
print(content)
print(list(content))

Mini-project — log file

from datetime import datetime

def log(message, file="app.log"):
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with open(file, "a") as f:
        f.write(f"[{timestamp}] {message}\n")

# Log a few events
log("App started")
log("User logged in")
log("User clicked button")
log("App stopped")

# Read the log
with open("app.log") as f:
    print(f.read())

Practice

What does this print?

Expected: second

with open("note.txt", "w") as f:
    f.write("first")
with open("note.txt", "w") as f:    # 'w' overwrites
    f.write("second")
with open("note.txt") as f:
    print(f.read())

Append to the file without losing existing content

Expected: helloworld

with open("data.txt", "w") as f:
    f.write("hello")
with open("data.txt", "w") as f:    # bug: 'w' wipes the file
    f.write("world")
with open("data.txt") as f:
    print(f.read())

Quiz — Quick check

What you remember

Q1. Which file mode appends to the end of an existing file?

  • "r"
  • "w"
  • "a"
  • "x"

Why: "w" wipes the file first (destroying everything). "a" opens it for writing but positions the cursor at the end. "r" is read-only.

Q2. Why use with open(...) as f: instead of f = open(...)?

  • It's faster
  • It auto-closes the file even if an exception occurs
  • It's the only way to read
  • No real difference

Why: The with statement guarantees f.close() runs no matter what. Without it, an exception inside the block leaves the file open — eventually causing resource leaks.

Q3. What does json.dump(data, f) do?

  • Returns a JSON string
  • Writes the data as JSON to the file f
  • Pretty-prints to the terminal
  • Validates the JSON

Why: dump writes to a file object. dumps (with the s) returns a string. Same for load vs loads for reading.

Common doubts

What happens to my file if my program crashes before f.close()?

The OS will eventually flush the buffer and close the file when the process ends, but buffered writes may be lost and on some systems the file can stay locked. Always use with open(...) as f: — it closes the file cleanly even if an exception interrupts the block.

Why does my CSV have blank lines between rows on Windows?

Because csv.writer writes \r\n and the file is also adding \n. Fix: open the file with newline="":

with open("data.csv", "w", newline="") as f:
    csv.writer(f).writerows(rows)

When should I use \"rb\" instead of \"r\"?

Use binary mode ("rb", "wb") for any file that isn't human-readable text: images, PDFs, compressed files, audio, executables. Text mode ("r") decodes bytes into a string using an encoding and translates newlines — both of which corrupt binary data.

What's next

Exception Handling