Files and CSV data
Read and write files, and turn a spreadsheet export into Python data.
Why it matters
Real data lives in files. Think of CSV exports from ad platforms and CRMs, documents you want a model to summarize, example prompts and replies for testing, and logs of everything your app sent to a model. Before you can analyze, embed or train on any of it, you need to get it into Python. Then you need to write your results back out.
What you'll be able to do
- Write text to a file and read it back with open() and with
- Process a file one line at a time
- Read a CSV export into rows you can calculate with, and write one
- Build file paths with pathlib
What it covers
- Writing and reading a file
- One line at a time
- CSV: spreadsheets as text
- Writing a CSV
- Worked example: from export to answer
- Three ways to lose an afternoon
- Close your files
Writing and reading a file
The academy runs Python inside your browser, with its own small file system. Files you create here live in this sandbox, not on your computer. But the code is exactly what you'd run on your own machine.
Working with a file is like using a notebook: open it, read or write, then close it.
with open("notes.txt", "w") as f: f.write("Q4 plan\n") f.write("Raise search budget\n") f.write("Test new subject lines\n") with open("notes.txt") as f: contents = f.read() print(contents)
open(path, mode) opens a file and gives you a file object, here named f. The mode says what you'll do with it:
| Mode | Meaning |
|---|---|
"r" | read (the default if you leave the mode out) |
"w" | write: creates the file, or erases an existing one first |
"a" | append: adds to the end of the file |
write doesn't add line breaks for you. \n inside a string is the newline character. It marks the end of a line.
with ... as f: makes sure the file is closed when the indented block ends, even if an error happens partway through. Always open files this way. A file left open can lose data that was written but not yet saved.
Tip: add
encoding="utf-8"when you open text files on your own computer, for exampleopen("notes.txt", encoding="utf-8"). Then accents, non-English text and emoji read correctly on every system.
One line at a time
Looping over a file object gives you its lines one by one. Each line still ends with its \n, so .strip() it:
with open("subjects.txt", "w") as f: f.write("Last chance: 30% off\n") f.write("New arrivals you'll love\n") f.write("Your weekly picks\n") with open("subjects.txt") as f: for line in f: line = line.strip() print(len(line), line)
f.readlines() gives you all the lines as a list instead. Looping is better for big files, because Python only holds one line in memory at a time. A 2 GB log file is fine to loop over. It would be painful to load all at once.
Appending is how logs grow:
with open("log.txt", "w") as f: f.write("batch 1 sent\n") with open("log.txt", "a") as f: f.write("batch 2 sent\n") with open("log.txt") as f: print(f.read())
open("report.txt", "w"). What happens to what was already in it?Show the answer
It's erased as soon as the file is opened
"w" starts from an empty file. Use "a" to add to the end instead.
CSV: spreadsheets as text
When you export from Google Ads, a CRM or a spreadsheet, you usually get a CSV (comma-separated values) file. It's plain text: one row per line, with values separated by commas. The first line holds the column names:
campaign,channel,spend,clicks Spring Sale,search,3120.5,5470 Retargeting,display,1890,2050
You could split each line on commas yourself. But that breaks as soon as a value contains a comma. CSV handles that by putting such values in quotes. Python's csv module understands the rules, so you don't have to.
csv.DictReader reads each row as a dictionary keyed by the column names. So you can ask for row["spend"] by name, instead of remembering it's the third column. One catch: every value comes back as a string, because a CSV file is just text. Convert numbers with float() or int() before doing math.
import csv with open("campaigns.csv", "w", newline="") as f: f.write("campaign,channel,spend,clicks\n") f.write("Spring Sale,search,3120.5,5470\n") f.write("Retargeting,display,1890,2050\n") f.write('"Brand Video, Q4",video,6400,3800\n') total_spend = 0 with open("campaigns.csv", newline="") as f: for row in csv.DictReader(f): spend = float(row["spend"]) total_spend += spend print(row["campaign"], "|", row["channel"], "|", spend) print("Total spend:", total_spend)
It prints the three campaigns, then Total spend: 11410.5. The third campaign's name contains a comma, and the quotes keep it as one value. The newline="" argument lets the csv module handle line endings itself. The Python docs recommend it whenever you open a file for csv.
{'channel': 'email', 'spend': '40'}. What does row["spend"] * 2 give?Show the answer
'4040'
Every CSV value is a string, and a string times 2 repeats it. Convert first: int(row["spend"]) * 2 is 80.
Look at row in the variables panel: {'channel': 'search', 'spend': '120'}. The spend is the text '120', which is why line 9 converts it with int() before adding.
Writing a CSV
csv.writer writes rows from lists. csv.DictWriter writes rows from dictionaries, once you give it the column order:
import csv rows = [ {"campaign": "Spring Sale", "roas": 5.08}, {"campaign": "Brand Video", "roas": 0.45}, ] with open("report.csv", "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=["campaign", "roas"]) writer.writeheader() for row in rows: writer.writerow(row) with open("report.csv") as f: print(f.read())
That file opens cleanly in Excel, Numbers or Google Sheets.
Paths with pathlib
A path is a file's address, like exports/summary.txt. The pathlib module treats paths as objects. That's tidier than gluing strings together:
from pathlib import Path folder = Path("exports") folder.mkdir(exist_ok=True) report = folder / "summary.txt" report.write_text("ROAS up 12% week over week\n") print(report) print(report.exists(), report.suffix, report.name) print(report.read_text()) for p in folder.iterdir(): print("Found:", p.name)
What each piece does:
- The
/operator joins path pieces with the right separator for your operating system. write_textandread_textare shortcuts for a quick write or read withoutopen.mkdir(exist_ok=True)creates a folder unless it's already there.
On your own computer, Path.home() / "Downloads" is your Downloads folder.
with open("a.txt", "w") as f: f.write("one\n") with open("a.txt", "a") as f: f.write("two\n") with open("a.txt", "w") as f: f.write("three\n") with open("a.txt") as f: print(f.read().strip())
Show the answer
three
The last "w" erases the file before writing, so only "three" is left.
Worked example: from export to answer
An email platform exported subject lines with sends and opens. Find each open rate and the best subject line.
import csv with open("email_export.csv", "w", newline="") as f: f.write("subject,sends,opens\n") f.write("Last chance: 30% off,24000,6240\n") f.write("New arrivals you'll love,22000,4180\n") f.write("Your weekly picks,23500,3290\n") best_subject = "" best_rate = 0 with open("email_export.csv", newline="") as f: for row in csv.DictReader(f): rate = int(row["opens"]) / int(row["sends"]) print(f"{rate:.1%} {row['subject']}") if rate > best_rate: best_rate = rate best_subject = row["subject"] print("Best:", best_subject)
It prints open rates of 26.0%, 19.0% and 14.0%, then Best: Last chance: 30% off.
The first block only creates the export, so the example can run. With a real export you'd skip it. The loop turns the text values into numbers, works out each open rate, and keeps track of the best one so far. {rate:.1%} formats a number as a percentage with one decimal place. The same dozen lines would handle an export with 50,000 rows.
Three ways to lose an afternoon
"w" empties the file first. Opening a file in write mode wipes it the moment the line runs, before you've written anything. To add to the end instead, use "a". When the data matters, write to a new file, and replace the old one only when that has worked.
Everything in a CSV is text. A CSV file has no types. row["score"] gives you "82", and comparing that with the number 70 is an error. Convert first: int(row["score"]) >= 70.
Don't split CSV lines by hand. line.split(",") looks fine until a value has a comma in it, such as "Boots, leather". The csv module understands the quoting rules, so let it do the work.
One more small thing: write doesn't start a new line for you. Add \n yourself, or everything ends up on one long line.
Close your files
Using with open(...) closes the file for you, even if an error happens partway through.
Practice what you just read
- 7 code examples you can edit and run
- a practice exercise that checks your code
- a 5-question quiz
- 5 review cards for spaced repetition
Try a quiz question
Why open files with with open(...) as f:?