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

Try it — edit and run

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:

Try it — edit and run

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.

Check your understanding
1/5

What type is every value read from a CSV file, before any manual conversion?

Exercise · Graded

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.