Chapter 8: Working with Files
Every program you've written so far has the same problem: the moment it stops running, everything is gone. Variables, lists, results — all erased. Run the grade tracker from Chapter 6 and enter thirty students — close the terminal, and they've vanished.
Files fix this. A file stores data on your hard drive permanently. Your program can write data to a file when it finishes, and read it back the next time it runs. That's how every real application works — notes apps, spreadsheets, web servers, games. They all read and write files.
By the end of this chapter you'll be able to read any text file, write data to a file, append to existing content, work with file paths, handle CSV data, and deal gracefully with files that don't exist.
How Files Work in Python
Working with a file follows three steps every time:
- Open the file — tell Python which file and what you want to do (read, write, or append).
- Do the work — read from it or write to it.
- Close the file — release it so other programs can use it and your data is saved.
Python gives you a shortcut for steps 1 and 3 together — the with statement. You'll use it for almost everything.
File Modes
When you open a file, you tell Python how you want to use it. This is called the mode.
| Mode | Meaning | File must exist? | Creates file? | Overwrites? |
|---|---|---|---|---|
"r" |
Read | Yes | No | No |
"w" |
Write | No | Yes | Yes |
"a" |
Append | No | Yes | No |
"r+" |
Read and write | Yes | No | No |
"x" |
Create (fails if exists) | No | Yes | No |
The two you'll use 95% of the time are "r" (read) and "w" (write). "a" is essential when you want to add to a file without erasing what's already there.
Reading a File
read() — Read the whole file at once
First, create a file to read. Open any text editor, type this, and save it as notes.txt in the same folder as your Python file:
Python is fun.
Files make data last.
I am learning a lot.
Now read it:
with open("notes.txt", "r") as file:
content = file.read()
print(content)
Output:
Python is fun.
Files make data last.
I am learning a lot.
with open("notes.txt", "r") as file: opens the file for reading and assigns it to the variable file. Everything indented under with can use file. When the indented block ends, Python automatically closes the file — even if an error occurs.
file.read() returns the entire contents of the file as one big string, including all newline characters (\n).
readlines() — Read into a list of lines
with open("notes.txt", "r") as file:
lines = file.readlines()
print(lines)
print(len(lines))
Output:
['Python is fun.\n', 'Files make data last.\n', 'I am learning a lot.\n']
3
readlines() gives you a list where each item is one line of the file. Notice the \n at the end of each line — that's the newline character. Strip it off with .strip() when you don't need it.
with open("notes.txt", "r") as file:
lines = file.readlines()
for line in lines:
print(line.strip())
Output:
Python is fun.
Files make data last.
I am learning a lot.
Looping line by line — The memory-efficient way
For large files, reading everything at once wastes memory. Loop over the file directly instead:
with open("notes.txt", "r") as file:
for line in file:
print(line.strip())
Output:
Python is fun.
Files make data last.
I am learning a lot.
Python reads one line at a time. This works for files of any size — even files too large to fit in memory. Get into this habit.
Your turn: Create a file called numbers.txt with five numbers, one per line (just type them in a text editor). Write a program that reads the file line by line, converts each line to a float, and prints the sum of all the numbers.
Writing to a File
Open with "w" and use file.write().
with open("output.txt", "w") as file:
file.write("Hello from Python!\n")
file.write("This is the second line.\n")
file.write("And the third.\n")
After this runs, output.txt exists on your drive with those three lines. Open it in a text editor to check.
Critical warning: Opening a file with "w" destroys all existing content. If output.txt already had data, it's gone the moment you open it with "w". Always be sure that's what you want.
You need to add \n yourself to start new lines — write() does not add them automatically.
# Write a list of items to a file
shopping = ["bread", "milk", "eggs", "butter", "coffee"]
with open("shopping.txt", "w") as file:
for item in shopping:
file.write(item + "\n")
shopping.txt will contain:
bread
milk
eggs
butter
coffee
Your turn: Write a program that asks the user to enter five items (use a loop and input()). Write each item to a file called my_list.txt, one per line. Open the file in a text editor to confirm it worked.
Appending to a File
"w" overwrites. "a" (append) adds to the end without touching what's already there.
with open("log.txt", "a") as file:
file.write("Program started.\n")
# Run it again — this line gets added, not replaced
with open("log.txt", "a") as file:
file.write("Program started again.\n")
After two runs, log.txt contains:
Program started.
Program started again.
Append mode is perfect for log files, diaries, and any record you want to grow over time.
from datetime import datetime
def log(message):
"""Append a timestamped message to the log file."""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open("app.log", "a") as file:
file.write(f"[{timestamp}] {message}\n")
log("User logged in")
log("User viewed dashboard")
log("User logged out")
app.log will contain something like:
[2026-03-09 14:23:01] User logged in
[2026-03-09 14:23:45] User viewed dashboard
[2026-03-09 14:24:12] User logged out
Every call adds a line. The file grows. That's a real application log.
File Paths: Absolute vs Relative
When you write open("notes.txt"), Python looks for notes.txt in the current directory — the folder your program is running from. This is a relative path.
A relative path is relative to where your program is:
open("notes.txt") # same folder as the script
open("data/notes.txt") # in a subfolder called data
open("../notes.txt") # one folder up
An absolute path gives the full location from the root of your drive:
open("C:/Users/Alice/Documents/notes.txt") # Windows
open("/home/alice/documents/notes.txt") # Mac/Linux
The modern way: pathlib
The pathlib module (covered fully in Chapter 16) gives you a clean, cross-platform way to work with file paths. Here's a taste:
from pathlib import Path
data_folder = Path("data")
file_path = data_folder / "notes.txt" # works on Windows AND Mac/Linux
with open(file_path, "r") as file:
print(file.read())
The / operator on Path objects joins path components — and it uses the right separator for whatever operating system you're on. Get into this habit over using raw strings for paths.
Handling Files That Don't Exist
If you try to open a file that doesn't exist in "r" mode, Python raises a FileNotFoundError and your program crashes.
with open("missing.txt", "r") as file: # FileNotFoundError!
content = file.read()
You'll learn full error handling in Chapter 13, but here's the essential pattern for files:
try:
with open("notes.txt", "r") as file:
content = file.read()
print(content)
except FileNotFoundError:
print("Error: The file 'notes.txt' was not found.")
print("Please create the file and try again.")
Now instead of crashing, you get a friendly message. Always wrap file reads in a try/except in real programs.
You can also check if a file exists before opening it:
from pathlib import Path
path = Path("notes.txt")
if path.exists():
with open(path, "r") as file:
print(file.read())
else:
print("File not found.")
Your turn: Write a program that tries to open a file called config.txt. If it exists, read and print its contents. If it doesn't exist, create it and write three lines of default settings to it, then print "Config file created."
Working with CSV Files
CSV stands for "Comma-Separated Values." It's the most common format for storing tabular data — rows and columns, like a spreadsheet. Almost every data source (Excel, Google Sheets, databases) can export to CSV.
A CSV file looks like this (students.csv):
name,grade,city
Alice,88,London
Bob,72,Paris
Carlos,95,Madrid
Diana,61,Tokyo
Reading a CSV file
Python's csv module makes this clean:
import csv
with open("students.csv", "r") as file:
reader = csv.reader(file)
for row in reader:
print(row)
Output:
['name', 'grade', 'city']
['Alice', '88', 'London']
['Bob', '72', 'Paris']
['Carlos', '95', 'Madrid']
['Diana', '61', 'Tokyo']
Each row becomes a list of strings. Skip the header row by calling next(reader) before the loop:
import csv
with open("students.csv", "r") as file:
reader = csv.reader(file)
next(reader) # skip the header row
for row in reader:
name, grade, city = row
print(f"{name} scored {grade} and is from {city}.")
Output:
Alice scored 88 and is from London.
Bob scored 72 and is from Paris.
Carlos scored 95 and is from Madrid.
Diana scored 61 and is from Tokyo.
DictReader — Rows as dictionaries
Even better: csv.DictReader turns each row into a dictionary, using the header row as keys.
import csv
with open("students.csv", "r") as file:
reader = csv.DictReader(file)
for row in reader:
print(f"{row['name']}: {row['grade']} ({row['city']})")
Output:
Alice: 88 (London)
Bob: 72 (Paris)
Carlos: 95 (Madrid)
Diana: 61 (Tokyo)
No need to remember column positions — you access by name. Much less error-prone.
Writing a CSV file
import csv
students = [
["name", "grade", "city"],
["Alice", 88, "London"],
["Bob", 72, "Paris"],
["Carlos", 95, "Madrid"],
]
with open("output_students.csv", "w", newline="") as file:
writer = csv.writer(file)
writer.writerows(students)
The newline="" argument prevents extra blank lines on Windows. Always include it when writing CSV on Windows.
Your turn: Create a file called products.csv with columns name, price, quantity. Add five products. Write a Python program that reads the file and prints the total value of inventory (price x quantity for each product, summed).
Putting It All Together: A Persistent To-Do List
Here's a program that saves and loads a to-do list from a file — so your tasks survive between runs.
from pathlib import Path
TODO_FILE = Path("todos.txt")
def load_todos():
"""Load todos from file. Return empty list if file doesn't exist."""
if not TODO_FILE.exists():
return []
with open(TODO_FILE, "r") as file:
return [line.strip() for line in file if line.strip()]
def save_todos(todos):
"""Save the list of todos to file."""
with open(TODO_FILE, "w") as file:
for todo in todos:
file.write(todo + "\n")
def show_todos(todos):
"""Print all todos with numbers."""
if not todos:
print("No todos yet.")
return
for i, todo in enumerate(todos, start=1):
print(f" {i}. {todo}")
todos = load_todos()
while True:
print("\n--- To-Do List ---")
show_todos(todos)
print("\n1. Add 2. Remove 3. Quit")
choice = input("Choice: ").strip()
if choice == "1":
task = input("New task: ").strip()
if task:
todos.append(task)
save_todos(todos)
print("Task added.")
elif choice == "2":
show_todos(todos)
try:
n = int(input("Remove number: "))
removed = todos.pop(n - 1)
save_todos(todos)
print(f"Removed: {removed}")
except (ValueError, IndexError):
print("Invalid number.")
elif choice == "3":
print("Goodbye!")
break
else:
print("Invalid choice.")
Run this program, add some tasks, quit, and run it again. Your tasks are still there. That's the difference files make.
What You Learned in This Chapter
- Files store data permanently — they survive after your program ends.
- File modes:
"r"(read),"w"(write — overwrites!),"a"(append — adds to end). - Always use
with open(...) as file:— it closes the file automatically. - Reading:
file.read()(whole file),file.readlines()(list of lines),for line in file(one line at a time — best for large files). - Writing:
file.write(text)— you must add\nyourself for new lines. - Relative paths are relative to where the script runs. Absolute paths are the full location.
- Use
pathlib.Pathfor cross-platform path handling. - Wrap file opens in
try/except FileNotFoundErrorto handle missing files gracefully. csv.readerreads CSV rows as lists.csv.DictReaderreads them as dictionaries.csv.writerwrites CSV files.
What's Next?
You can now read, write, and persist data. The next chapter brings everything together — variables, lists, loops, functions, and files — in a complete beginner project: a to-do list app built from scratch, step by step, with every decision explained.
Your turn: Write a program that reads a CSV file of products (name, price), applies a 10% discount to every price, and writes the updated data to a new CSV file called discounted.csv. Print a summary showing the original total value and the discounted total value.