Earlier this month I wanted to know what my coffee habit actually costs, and the answer lived in a 60-row CSV I keep in my notes folder. I very nearly installed pandas for this. The csv module — which ships with Python — did the whole job in a dozen lines, on a laptop with no dependencies and no internet if it came to that.
date,coffee,size,price
2026-03-02,latte,large,4.80
2026-03-03,filter,small,2.60
2026-03-05,latte,large,4.80
2026-03-06,flat white,small,3.40
import csv
with open("coffee_log.csv", newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
print(row["date"], row["coffee"], row["price"])
DictReader reads the header row and uses it as the keys, so every row is a dict — no counting columns, no row[2] that silently breaks when someone reorders the file. (There's a plain csv.reader that hands you lists instead; I stopped using it the day a spreadsheet gained a column in the middle.) Two things to know: every value is a string, so convert with float() before doing math; and that mysterious newline="" is doing something important, which we'll get to.
One more thing the module handles so I never have to: quoting. flat white needed no quotes above because it has no comma, but a field like latte, oat milk gets quoted by DictWriter on the way out and un-quoted by DictReader on the way back. The writer and reader agree with each other, which is most of what you need from a file format.
import csv
total = 0.0
count = 0
with open("coffee_log.csv", newline="", encoding="utf-8") as f:
for row in csv.DictReader(f):
if row["coffee"] == "latte" and row["size"] == "large":
total += float(row["price"])
count += 1
print(f"{count} large lattes, {total:.2f} in total")
2 large lattes, 9.60 in total
Twelve lines including the blank ones, no imports beyond the standard library, and the answer was worse than I expected.
newline="" gotchaWriting rows back is symmetric:
import csv
row = {"date": "2026-03-20", "coffee": "espresso", "size": "small", "price": "2.20"}
with open("coffee_log.csv", "a", newline="", encoding="utf-8") as f:
writer = csv.DictWriter(f, fieldnames=["date", "coffee", "size", "price"])
writer.writerow(row)
Now the gotcha. The csv module writes its own \r\n line endings. If you open the file in text mode without newline="", Python also translates \n to \r\n on the way out, so every row ends with \r\r\n. The symptom: Excel shows a blank row between every line of data, on Windows only, and the file still "works", so the bug can hide for months. newline="" tells Python to keep its hands off the line endings because the csv module knows what it's doing. One argument, once, forever. (For a brand-new file, call writer.writeheader() first so the header row lands before the data.)
pandas earns its keep when the job is analysis — joins, groupbys, reshaping, plotting. For open a table, filter, sum, append a row, it's a forklift for moving one bookshelf. The csv module has been in the standard library longer than I've been writing Python, opens files that any text editor and any spreadsheet can read, and all of its quirks fit in one paragraph. Small table jobs deserve small tools.