Python CSV Handling in Depth

Read and write CSV files correctly with csv.reader, DictReader and DictWriter, including quoting, encodings and type conversion.

Lesson 49 of 107Intermediate12 min read

A CSV file stores a table as plain text: one record per line, fields separated by commas, usually with a header row. Python's built-in csv module reads and writes it correctly, including the awkward cases, such as fields that contain commas, quotes or line breaks, that break simple split(",") code.

Python
import csv
with open("results.csv", newline="", encoding="utf-8") as file:
    for row in csv.DictReader(file):
        print(row["crew"], row["seconds"])

This lesson covers how CSV quoting works, reading rows as dictionaries, writing with an explicit column order, and the file settings that prevent blank lines and garbled characters. The examples process results from a rowing regatta. The file handling lesson showed a first CSV example; this one goes into depth.

CSV structure

CSV looks simple, but fields can contain the delimiter. The format handles that by quoting: a field wrapped in double quotes may contain commas and line breaks, and a literal double quote inside it is written twice:

Text
crew,club,seconds
Harbor Herons,"Riverside, North",83
"The ""Fast"" Four",Lakeside,79

line.split(",") on the second line gives four pieces instead of three, and leaves the quotes in place. The csv module parses it correctly:

Python
import csv, io
text = 'crew,club\nHarbor Herons,"Riverside, North"\n'
print(list(csv.reader(io.StringIO(text))))
# [['crew', 'club'], ['Harbor Herons', 'Riverside, North']]

csv.reader returns each row as a list of strings. Not every "CSV" uses commas: European spreadsheet exports often use semicolons, and tab-separated files are common. Pass delimiter=";" or delimiter="\t", or let csv.Sniffer().sniff(sample) guess the dialect from a sample of the file.

Dictionary rows

csv.DictReader uses the header row as keys and returns each data row as a dictionary, so you refer to columns by name instead of position:

Python
for row in csv.DictReader(file):
    seconds = int(row["seconds"])     # every value arrives as a string

Code that uses names keeps working if someone reorders the columns. Two details matter in practice:

  • Every value is a string, even if it looks like a number. Convert with int(), float() or date parsing before calculating, and handle ValueError for bad cells.
  • Missing and extra fields don't raise errors. If a row is short, the missing keys get the value None (or restval); if it has extra fields, they're collected in a list under the key None (or restkey). Check for these when the data matters.

Headers sometimes carry stray spaces or a byte-order mark from Excel. Opening the file with encoding="utf-8-sig" removes the BOM, and skipinitialspace=True ignores spaces after delimiters.

Writing

csv.writer writes lists; csv.DictWriter writes dictionaries and needs an explicit fieldnames list, which fixes the column order:

Python
import csv
results = [{"crew": "Harbor Herons", "seconds": 85},
           {"crew": "Canal Otters", "seconds": 81}]

with open("adjusted.csv", "w", newline="", encoding="utf-8") as file:
    writer = csv.DictWriter(file, fieldnames=["crew", "seconds"])
    writer.writeheader()
    writer.writerows(results)

Values are converted with str() and quoted automatically when they contain the delimiter, quotes or newlines. quoting=csv.QUOTE_ALL quotes every field, which some systems require.

If a dictionary has a key that isn't in fieldnames, DictWriter raises ValueError by default. That's deliberate: silently dropping data is rarely what you want. Pass extrasaction="ignore" only when you've decided extra keys should be discarded.

File settings

Two open() settings prevent the most common CSV problems:

  • newline="" lets the csv module handle line endings itself. Without it, quoted fields containing line breaks can be misread, and on Windows writing produces a blank line between every row. The Python documentation recommends newline="" for both reading and writing.
  • encoding must match the file. Use "utf-8" for files you create. Files exported from Excel may be "utf-8-sig" (UTF-8 with a BOM) or a legacy code page such as "cp1252"; a UnicodeDecodeError or odd characters like é mean the encoding is wrong.

For quick tests and examples, io.StringIO gives a file-like object over a string, so you can run CSV code without creating files. For large analytical work, pandas reads CSV with pd.read_csv(), which converts types automatically. The csv module remains the right tool for streaming rows one by one with minimal dependencies.

Independent practice and a worked check

Task: import race rows, validate missing fields and export adjusted times with an explicit header.

Python
import csv, io

data = io.StringIO("crew,seconds\nHerons,83\nOtters,\nKites,77\n")
rows = [r for r in csv.DictReader(data) if r["seconds"].strip()]
for r in rows:
    r["seconds"] = int(r["seconds"]) + 2

out = io.StringIO()
w = csv.DictWriter(out, fieldnames=["crew", "seconds"], lineterminator="\n")
w.writeheader()
w.writerows(rows)
print(out.getvalue())

This prints a header and two rows, Herons,85 and Kites,79; the Otters row is skipped because its time is blank. For the worked check, a numeric field returned by DictReader has type str until you convert it.

Reference and next practice

The csv module documentation describes dialects, quoting constants and every reader and writer option. Continue with JSON handling and data cleaning. Practise with the Python exercises and Python quiz.

CSV Handling in Depth Summary

  • CSV structure: CSV is a text format with rows and fields whose quoting rules can protect embedded delimiters.
  • Dictionary rows: DictReader uses headers as dictionary keys and returns text values.
  • Writing: DictWriter writes named fields using an explicit field order.
  • File settings: Opening CSV files with newline empty lets the CSV module manage line endings.

CSV Handling in Depth Examples

Each example shows the code, the output it produces and what each part does.

Example 1

CSV Handling in Depth in a small program

Python
import csv
from io import StringIO
source = StringIO("crew,seconds\nHarbor Herons,83\nCanal Otters,79\n")
for row in csv.DictReader(source):
    print(row["crew"], int(row["seconds"]) + 2)
Output
Harbor Herons 85
Canal Otters 81
Line-by-line explanation
  1. Line 3StringIO stands in for a file containing a header and two rows.
  2. Line 4DictReader turns each row into a dictionary keyed by the header names.
  3. Line 5seconds arrives as text, so it's converted with int() before adding the 2-second penalty.
Example 2

Validating and exporting results

Python
import csv, io

source = io.StringIO(
    "crew,club,seconds\n"
    'Harbor Herons,"Riverside, North",83\n'
    "Canal Otters,Lakeside,\n"
    "Mill Swifts,Weir Club,91\n"
)

valid, rejected = [], []
for line_no, row in enumerate(csv.DictReader(source), start=2):
    if not row["seconds"]:
        rejected.append(f"line {line_no}: no time for {row['crew']}")
        continue
    row["seconds"] = int(row["seconds"]) + 2
    valid.append(row)

output = io.StringIO()
writer = csv.DictWriter(output, fieldnames=["crew", "club", "seconds"], lineterminator="\n")
writer.writeheader()
writer.writerows(valid)
print(output.getvalue(), end="")
print(*rejected, sep="\n")
Output
crew,club,seconds
Harbor Herons,"Riverside, North",85
Mill Swifts,Weir Club,93
line 3: no time for Canal Otters
Line-by-line explanation
  1. Line 5The quoted club name contains a comma but is still one field.
  2. Line 11Counting from 2 matches the file's line numbers, because line 1 is the header.
  3. Line 19DictWriter re-quotes the club name automatically when writing it out.

Common CSV Handling in Depth Mistakes

Splitting CSV lines on commas

Don't
for line in open("results.csv"):
    crew, club, seconds = line.strip().split(",")   # breaks on "Riverside, North"
Do
with open("results.csv", newline="", encoding="utf-8") as f:
    for crew, club, seconds in csv.reader(f):
        ...

The csv module understands quoting; split() doesn't.

Forgetting newline="" when writing

Don't
with open("out.csv", "w") as f:
    csv.writer(f).writerows(rows)     # blank lines between rows on Windows
Do
with open("out.csv", "w", newline="", encoding="utf-8") as f:
    csv.writer(f).writerows(rows)

CSV Handling in Depth in Real Projects

CSV in everyday data work

CSV is the common language between spreadsheets, databases, accounting systems and analytics tools. Python scripts import exports from one system, clean and validate them, and write files for another: bank statements into budgeting tools, survey results into reports, product lists into online shops.

Robust import scripts follow the pattern in this lesson: read with DictReader, convert and validate each field, collect rejected rows with line numbers, and write output with an explicit column order. Spreadsheet users can then fix exactly the rows listed, instead of guessing why totals look wrong.

CSV Handling in Depth FAQ

How do I read a CSV file into a list of dictionaries?

with open("file.csv", newline="", encoding="utf-8") as f: rows = list(csv.DictReader(f)). Each row becomes a dictionary keyed by the header names, with every value as a string.

Why does my CSV file have blank lines between rows?

The file was opened for writing without newline="", so on Windows each row ends with an extra line break. Open it with open(path, "w", newline="", encoding="utf-8").

Should I use the csv module or pandas?

Use the csv module for simple reading and writing, streaming large files row by row, and scripts with no third-party dependencies. Use pandas when you need to analyse, filter, group or join tables, since it converts column types and provides fast operations on whole columns.

How do I read a CSV with a semicolon delimiter?

Pass delimiter=";" to csv.reader or csv.DictReader. For tab-separated files use delimiter="\t". If you're unsure, csv.Sniffer().sniff(sample_text) can detect the delimiter from a sample.

How do I skip the header row with csv.reader?

Call next(reader) once before the loop; it returns the header list, which you can keep or discard. With DictReader the header is consumed automatically and used as dictionary keys.

CSV Handling in Depth Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Multiple choiceeasy

What type is a numeric field returned by DictReader?

Multiple choicemedium

Which review concern is valid for csv handling in depth?

CSV Handling in Depth Quiz

2 questions to check what you learned.

  1. What type is a numeric field returned by DictReader?

    • int
    • str
    • Python ignores the distinction
  2. Which statement correctly describes writing?

    • DictWriter writes named fields using an explicit field order.
    • Only the example output matters, not the rule
    • Every operation has the same behavior