Lesson 34 · Intermediate · 19 min

Python CSV Files

Read and write tabular text without splitting lines by hand.

CSVDictReadercsv.writerquoting

The accountant wants a CSV, and the drink 'Latte, oat' breaks every naive split. The csv module knows about quotes. What does DictReader assume about the first row?

CSV looks simple until a value contains a comma, a quote, or a newline. The csv module handles those details and can expose each row as a dictionary keyed by the header. Use StringIO for small examples so the exercise does not leave files behind. You will also handle uneven columns, strict writers, and byte-order marks.

Read rows as dictionaries

DictReader treats the first row as field names by default. Each subsequent row becomes a dictionary. This is easier to read than remembering column positions, especially when the source has many columns.

import csv
from io import StringIO

raw = "name,score\nAda,92\nGrace,88\n"
rows = list(csv.DictReader(StringIO(raw)))
print(rows[0]["name"])

Header names become keys

Write rows with a writer

csv.writer writes one row at a time and adds the line terminator. Pass a list or tuple of fields. For a small in-memory example, write into StringIO and inspect getvalue().

import csv
from io import StringIO

buffer = StringIO()
writer = csv.writer(buffer)
writer.writerow(["name", "score"])
writer.writerow(["Ada", 92])
print(buffer.getvalue().strip())

Build CSV text in memory

Quote fields that need it

A comma inside a field must be protected by quotes. QUOTE_ALL quotes every field, which is useful when another tool expects it. The reader will remove the quotes and return the original value.

import csv
from io import StringIO

buffer = StringIO()
writer = csv.writer(buffer, quoting=csv.QUOTE_ALL)
writer.writerow(["a,b", "c"])
print(buffer.getvalue().strip())

Commas inside a field

DictReader: missing and extra columns

DictReader uses the header row as keys. A short row fills missing fields with None; extra fields go into the key named by restkey. Check both cases at the boundary instead of assuming every row has exactly the expected shape.

import csv
import io

text = "name,price\nlatte,4.5\ntea\nmocha,5.0,note\n"
reader = csv.DictReader(io.StringIO(text), restkey="extra")
for row in reader:
    print(row["name"], row.get("price"), row.get("extra"))

A header does not guarantee a full row

DictWriter: field order and strict extras

fieldnames fixes the column order. extrasaction='raise' turns an unexpected key into an error instead of silently dropping it. A fixed lineterminator makes output reproducible across platforms and avoids double newlines on Windows.

import csv
import io

out = io.StringIO(newline="")
fields = ["name", "price"]
writer = csv.DictWriter(
    out, fieldnames=fields, extrasaction="raise", lineterminator="\n"
)
writer.writeheader()
writer.writerow({"name": "latte", "price": 4.5})
try:
    writer.writerow({"name": "tea", "price": 3.0, "note": "hot"})
except ValueError as error:
    print(type(error).__name__, error)
print(repr(out.getvalue()))

Fail loudly on an unexpected column

Handle a BOM and line endings at the boundary

Spreadsheet exports often start with a UTF-8 byte-order mark. Decode with utf-8-sig so the first header does not become \ufeffname. Open text streams with newline='' so the csv module, not the platform, controls row endings.

import csv
import io

raw = b"\xef\xbb\xbfname,price\r\nlatte,4.5\r\n"
text = raw.decode("utf-8-sig")
reader = csv.DictReader(io.StringIO(text, newline=""))
row = next(reader)
print(row["name"], row["price"])

out = io.StringIO(newline="")
writer = csv.writer(out, lineterminator="\n")
writer.writerow(["tea", 3.0])
print(repr(out.getvalue()))

Decode the BOM once, then parse

Key points

  • Use csv.reader or csv.DictReader instead of splitting on commas.
  • DictReader uses the first row as headers by default.
  • writerow writes one row and adds a line terminator.
  • Quoting protects commas, quotes, and newlines inside fields.
  • Use StringIO for small in-memory examples.
  • Check row shape; make writer extras strict; decode utf-8-sig and control newlines.

Common mistakes

  • Using line.split(",") when a field contains a comma.
  • Forgetting that DictReader values are strings unless you convert them.
  • Opening a file for csv writing without newline="" on platforms that translate line endings.
  • Opening CSV text without newline='' and then blaming csv for blank lines.
Your turn

Parse 'name,score\nAda,92\n' with DictReader and print Ada's score.

Try it yourself

Change the values, predict the output, then run the code. The Python runtime is downloaded the first time you press Run, so the first run can take a few seconds.

In-browser PythonRuns locally · no account
First run downloads Pyodide from a CDN. Use Run code after editing.

Practice this lesson

12 questions with immediate explanations.

See all 12 answersStart practice
← enumslogging →