🐍 Python Course · Stage 3 · Lesson 41 of 89 · csv — CSV Files
Course Overview →

🔵 Read Beginner and Intermediate tabs. The Expert tab has the internals — worth reading once you finish Stage 3.

← functools — Function Tools subprocess — External Programs →
thecodex.expert · The Codex Family of Knowledge
Python

csv — Reading and Writing CSV Files

The csv module reads and writes comma-separated values files correctly — handling quoting, escaping, different delimiters, and the edge cases that break naive string splitting.

Python 3.13 docs.python.org csv Last verified:
Canonical Definition

csv is a standard library module for reading and writing CSV (Comma-Separated Values) files. It correctly handles quoted fields, embedded commas and newlines, custom delimiters, and dialects. The DictReader and DictWriter classes map rows to and from dictionaries using the header row.

🟩 Beginner

Reading and writing CSV files correctly

What you'll learn: How to read and write CSV files with csv.reader and csv.writer, and the one rule that prevents the most common CSV bug — always open with newline="".

How to read this tab: Create a small CSV file with a field that contains a comma (inside quotes). Read it with csv.reader and watch it handle the comma correctly — something a naive line.split(",") would get wrong.

⏱ 25 min📄 2 sections🔶 Prerequisite: File I/O
The idea

CSV files look simple — values separated by commas — but they have tricky edge cases: a field might contain a comma inside quotes, or a newline. The csv module handles all of this correctly so you don't have to. Never parse CSV by splitting on commas yourself.

newline="" is mandatory — this is the #1 CSV bug. Always write open("file.csv", newline="", encoding="utf-8"). The csv module handles line endings itself; if you omit newline="", Python's universal newline translation interferes and you get spurious blank rows on Windows. Memorise this — it catches almost everyone once.

Reading a CSV file

The csv.reader object reads a file row by row, giving each row as a list of strings. Always open CSV files with newline="" — this is required for the csv module to handle line endings correctly across platforms.

Pythoncsv_read.py
import csv

# A CSV file: data.csv
# name,age,city
# Priya,30,Mumbai
# Arjun,28,"New Delhi, India"   <- note the comma inside quotes

with open("data.csv", newline="", encoding="utf-8") as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)
# ['name', 'age', 'city']
# ['Priya', '30', 'Mumbai']
# ['Arjun', '28', 'New Delhi, India']   <- comma handled correctly

# Skip the header row
with open("data.csv", newline="", encoding="utf-8") as f:
    reader = csv.reader(f)
    header = next(reader)        # read the first row separately
    for row in reader:
        name, age, city = row
        print(f"{name} is {age}")
Always use newline="" when opening CSV files

The csv module handles its own line-ending translation. If you open without newline="", Python's universal newline translation interferes, causing blank rows to appear on Windows. This is the single most common csv bug. open("file.csv", newline="", encoding="utf-8") — every time.

💡

csv.writer automatically quotes fields that need it. If a field contains a comma, a quote, or a newline, the writer wraps it in quotes (and doubles internal quotes). You don't need to handle this yourself — that's the entire point of using the csv module instead of manual string joining.

Writing a CSV file

The csv.writer object writes rows to a file, automatically quoting fields that contain commas, quotes, or newlines.

Pythoncsv_write.py
import csv

rows = [
    ["name", "age", "city"],
    ["Priya", 30, "Mumbai"],
    ["Arjun", 28, "New Delhi, India"],   # comma will be auto-quoted
]

with open("output.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerows(rows)        # write all rows at once
    # or write one at a time:
    # for row in rows:
    #     writer.writerow(row)

# output.csv contains:
# name,age,city
# Priya,30,Mumbai
# Arjun,28,"New Delhi, India"    <- automatically quoted

✅ Beginner tab complete

  • I always open CSV files with newline="" and encoding="utf-8"
  • I never parse CSV by splitting on commas myself
  • I can read rows with csv.reader and skip the header with next()
  • I can write rows with csv.writer and writerows()

Continue to subprocess →

🔵 Intermediate

DictReader, DictWriter, and dialects

What you'll learn: DictReader and DictWriter for working with CSV rows as dictionaries keyed by column name. Handling tab-separated, semicolon-separated, and custom-delimiter files with dialects.

How to read this tab: Convert a csv.reader loop to a DictReader loop. Accessing row["name"] instead of row[0] makes code far more readable and robust to column reordering.

⏱ 25 min📄 2 sections🔶 Prerequisite: Beginner tab

DictReader is almost always better than reader. Accessing row["email"] is clearer than row[3], and it doesn't break when columns are reordered or added. The one thing to remember: every value is still a string. row["age"] is "30", not 30 — convert with int(row["age"]) when you need numbers.

DictReader and DictWriter

The most convenient way to work with CSV files that have a header row. DictReader gives each row as a dictionary keyed by the column names; DictWriter writes dictionaries back to CSV.

Pythoncsv_dict.py
import csv

# DictReader — each row is a dict keyed by the header
with open("data.csv", newline="", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for row in reader:
        # row is {'name': 'Priya', 'age': '30', 'city': 'Mumbai'}
        print(f"{row['name']} lives in {row['city']}")
# Priya lives in Mumbai
# Arjun lives in New Delhi, India

# DictWriter — write dicts to CSV
people = [
    {"name": "Priya", "age": 30, "city": "Mumbai"},
    {"name": "Arjun", "age": 28, "city": "Delhi"},
]

with open("output.csv", "w", newline="", encoding="utf-8") as f:
    fieldnames = ["name", "age", "city"]
    writer = csv.DictWriter(f, fieldnames=fieldnames)
    writer.writeheader()          # write the header row
    writer.writerows(people)      # write all the dicts

# Note: all values are read as STRINGS — convert types yourself
with open("data.csv", newline="", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    total_age = sum(int(row["age"]) for row in reader)  # int() needed
    print(f"Total age: {total_age}")
📎

"CSV" doesn't always mean commas. European Excel exports often use semicolons (because comma is the decimal separator in many locales). Tab-separated files (.tsv) use tabs. Pass delimiter=";" or delimiter="\t" to handle these. csv.Sniffer can auto-detect the delimiter from a sample if you don't know it in advance.

Dialects and custom delimiters

Not all "CSV" files use commas. Tab-separated (TSV), semicolon-separated (common in European locales), and pipe-separated files are handled by specifying the delimiter or a dialect.

Pythoncsv_dialects.py
import csv

# Tab-separated values
with open("data.tsv", newline="", encoding="utf-8") as f:
    reader = csv.reader(f, delimiter="\t")
    for row in reader:
        print(row)

# Semicolon-separated (common in European Excel exports)
with open("data_eu.csv", newline="", encoding="utf-8") as f:
    reader = csv.reader(f, delimiter=";")
    for row in reader:
        print(row)

# Control quoting behaviour
with open("output.csv", "w", newline="", encoding="utf-8") as f:
    # QUOTE_ALL — quote every field
    writer = csv.writer(f, quoting=csv.QUOTE_ALL)
    writer.writerow(["a", "b", "c"])   # "a","b","c"

# Register a custom dialect for reuse
csv.register_dialect("pipes", delimiter="|", quoting=csv.QUOTE_MINIMAL)
with open("data.txt", newline="", encoding="utf-8") as f:
    reader = csv.reader(f, dialect="pipes")
    for row in reader:
        print(row)

# Sniffer — auto-detect the dialect
with open("unknown.csv", newline="", encoding="utf-8") as f:
    sample = f.read(1024)
    f.seek(0)
    dialect = csv.Sniffer().sniff(sample)
    reader = csv.reader(f, dialect)
Commonly confused
CSV values are always strings. csv.reader and DictReader return every field as a string — including numbers. row["age"] is "30", not 30. You must convert: int(row["age"]). The csv module does not infer types.
For large data analysis, use pandas instead. The csv module is perfect for simple reading/writing and streaming large files row by row. But for filtering, aggregating, and analysing tabular data, pandas (pd.read_csv) is far more powerful. csv for I/O; pandas for analysis.

✅ Intermediate tab complete

  • I can use DictReader to access fields by column name: row["name"]
  • I can use DictWriter with writeheader() and writerows()
  • I remember that all CSV values are strings — I convert types myself with int(), float()
  • I can handle TSV and semicolon-delimited files with the delimiter parameter

Continue to subprocess →

🔴 Expert

RFC 4180, edge cases, and the C implementation

What you'll learn: Why CSV is harder than it looks (RFC 4180), the edge cases that break naive parsing (embedded commas, quotes, newlines), and why csv.reader is implemented in C for performance.

How to read this tab: Read this when you need to understand why newline="" is required, or when debugging CSV files that have quoted fields spanning multiple lines.

⏱ 20 min📄 1 section🔶 Prerequisite: After Stage 3

The CSV format problem and RFC 4180

CSV is deceptively complex because it was never formally standardised until RFC 4180 (2005) — and even that is only a guideline that many tools ignore. The csv module exists precisely because correct CSV parsing requires a state machine, not a string split. Consider the edge cases: a field containing a comma must be quoted; a field containing a quote must escape it by doubling; a field can contain a newline if quoted.

Pythoncsv_edge_cases.py
import csv, io

# These edge cases break naive line.split(",") parsing.
# A real CSV file on disk would contain (between the dashes):
# ---
# name,quote,note
# Priya,"She said ""hello""","Line 1
# Line 2"
# Arjun,"Comma, inside",normal
# ---
# Field 2 has escaped quotes; field 3 spans two physical lines;
# Arjun's quote field contains a comma. The csv module handles all three.

with open("tricky.csv", newline="", encoding="utf-8") as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)
# ['name', 'quote', 'note']
# ['Priya', 'She said "hello"', 'Line 1\nLine 2']  <- embedded newline
# ['Arjun', 'Comma, inside', 'normal']             <- embedded comma# ['Priya', 'She said "hello"', 'Line 1\nLine 2']  <- embedded newline!
# ['Arjun', 'Comma, inside', 'normal']              <- embedded comma!

# Why newline="" matters — the csv module needs to see raw \r\n
# to distinguish line endings WITHIN quoted fields from row separators.
# Without newline="", Python translates \r\n to \n before csv sees it,
# corrupting quoted fields that legitimately contain \r\n.

# Performance: csv.reader is implemented in C (_csv module)
# It processes millions of rows per second — far faster than
# any pure-Python parser. For a 1GB CSV, stream it row by row:
def process_large_csv(path):
    with open(path, newline="", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        for row in reader:       # one row at a time, constant memory
            yield process(row)

The csv module's reader and writer are implemented in C (the _csv extension module) for performance. The Python-level csv module wraps this with the convenient DictReader/DictWriter classes and dialect management. This is why csv.reader can process millions of rows per second while a hand-written Python parser would be orders of magnitude slower.

✅ Expert tab complete

  • I know CSV fields can contain commas, quotes, and newlines when quoted
  • I understand why newline="" is required (csv handles line endings itself)
  • I know csv.reader is implemented in C and can process millions of rows per second

Continue to subprocess →

Sources

1
Python Standard Library — csv. docs.python.org/3/library/csv.html.
2
PEP 305 — CSV File API. peps.python.org/pep-0305/.
3
RFC 4180 — Common Format and MIME Type for CSV Files. rfc-editor.org/rfc/rfc4180.
4
Python Standard Library — csv.DictReader and csv.DictWriter. docs.python.org/3/library/csv.html#csv.DictReader.
Source confidence: High Last verified: Primary source: docs.python.org csv