Working with CSV
CSV: rows of values, no built-in type information
CSV (Comma-Separated Values) is the other data format you’ll run into constantly — batch inputs, exported spreadsheets, eval datasets. Unlike JSON, it has no real structure beyond rows and columns — every value is just text, and there’s no built-in concept of numbers, booleans, or nesting:
name,model,temperature
research_agent,claude-sonnet,0.7
support_agent,claude-haiku,0.3(a plain CSV file, shown for reference — not Python code, not run live)
The first row is conventionally a header naming each column, but nothing
in the CSV format itself enforces that — it’s just a convention every
tool agrees to follow. Python’s csv module (standard library, like
json) handles the parsing, but it’s worth knowing upfront: CSV is a
plainer format than JSON, and that shows up directly in how you work with
it.
csv.reader — rows as lists
csv.reader(f) wraps an already-open file object — same pattern as
json.load(f) — and gives you something iterable, one list per row,
including the header row itself as an ordinary row. Note every value is a
string, including '0.7' — CSV has no concept of numeric types, so
temperature comes back as text and needs manual conversion
(float(row[2])) if you actually need it as a number.
csv.writer — the reverse direction
import csv
rows = [
["name", "model", "temperature"],
["research_agent", "claude-sonnet", "0.7"],
["support_agent", "claude-haiku", "0.3"],
]
with open("agents.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerows(rows)(writes a file — not run live in this format, but produces the exact file shown at the top of this section)
writer.writerows(rows) writes every row in one call; writer.writerow(row)
(singular) writes just one, useful inside a loop where rows are being
built up one at a time. newline="" in the open() call is CSV-specific
boilerplate worth just knowing to include — without it, some platforms
write an extra blank line between rows due to how the underlying file
mode handles line endings.
csv.DictReader and csv.DictWriter — rows as dicts
Working with each row as a plain list means remembering that
row[2] is the temperature, row[0] is the name — the same
position-dependent fragility
lists have compared to dicts.
csv.DictReader fixes this by using the header row to key each row by
column name instead of position:
DictReader automatically treats the first row as headers — it’s no
longer included as a data row the way it was with plain csv.reader. Each
row comes back as a dict keyed by column name, and row["temperature"]
is far more robust to a column getting reordered in the source file than
row[2] ever was. Values are still all strings, same caveat as before.
csv.DictWriter is the matching write side — it needs to know the column
names upfront (fieldnames), and can write a header row for you:
import csv
rows = [
{"name": "research_agent", "model": "claude-sonnet", "temperature": "0.7"},
{"name": "support_agent", "model": "claude-haiku", "temperature": "0.3"},
]
with open("agents.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["name", "model", "temperature"])
writer.writeheader()
writer.writerows(rows)(writes a file — not run live in this format, but produces the exact file shown at the top of this section)
fieldnames also determines column order in the output, and
writer.writeheader() writes that header row explicitly — DictWriter
doesn’t infer it automatically from the dicts, since a dict’s own keys
aren’t guaranteed to print in a specific order you should rely on
the way fieldnames being stated upfront is.
What type is every value read from a CSV file, before any manual conversion?
Read csv_path with csv.DictReader, convert each row's "temperature" from a string to a float, collect the results into a list of dicts, and write that list to json_path as JSON with indent=2.