LESSON 123 · DEEPER PYTHON30 min

Read and Write CSV with Quoting and Dialects

Read and write CSV records containing commas and quotation marks without splitting lines manually.

Example check: Worked and guided code samples are checked against CPython 3.13. Practice uses Pyodide 0.29.5 (Python 3.13) in your browser. How we check lessons

Video lesson: Read and Write CSV with Quoting and Dialects

The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube

Read the video transcript

Select a timestamp to watch that moment on YouTube.

  1. 00:00

    Imagine receiving a small people table in CSV form. The city is New York, NY, which contains a comma. The surrounding quotes say that comma is part of the city, but a line split on commas knows nothing about quotes. Before the first run, predict the number of fields and explain which one you expect to contain the city. This is why CSV is a format with rules rather than simply comma-separated text. We will first reproduce the broken output and then use the Python csv module to read and write the same values reliably. A CSV comma is a field separator only when it appears outside a quoted field.

  2. 00:41

    The address New York, NY belongs to one cell when surrounded by quotes. A string split on commas cannot know this; it turns one address into two fields. The csv module understands the format and handles quoted fields, doubled quotation marks, alternate delimiters, and record boundaries. Use a file opened with newline='' or an io.StringIO stream, then pass the stream to csv.reader or csv.DictReader. The browser lesson uses an in-memory stream so you can run it immediately. The broken run creates three pieces and leaves quote marks attached to two of them. A downstream importer might put New York in the city column and NY in an unexpected

  3. 01:23

    extra column. Worse, that bad row can look plausible if nobody checks the field count. The fix begins by passing a stream to csv.reader. We use io.StringIO to keep the exercise self-contained in the browser. For a real file, open it with newline='' and pass the file object to the same parser. The parser handles quoted commas and line boundaries together. First run a naïve split on a record with an embedded comma and count the fields. Then read the same text with csv.reader. The latter returns two cells because the comma inside quotes is data.

  4. 02:04

    csv.DictReader uses the header row as field names, which makes a practical parser easier to read than positional indexes. A blank optional cell remains an empty string, not None. For this exercise, name and city are required and each record must have exactly those two columns. Raise ValueError for a malformed header or missing value rather than silently accepting a shifted row. The diagnosis shows one header row and one two-cell data row. DictReader adds the header names to the parsed values, so code can ask for row['city'] instead of relying on index one. The parser does not know whether city may be blank or what headers your application

  5. 02:46

    expects. Those are separate rules. Next we write a row containing a quotation mark and inspect the output. The csv writer adds the quotes needed by the dialect and doubles the embedded quote. Joining strings by hand would fail on that value too. Writing CSV requires the same care. csv.writer quotes a field when needed and doubles embedded quote characters according to the dialect. Build a StringIO stream, write one row, and inspect the returned text. Do not construct output by joining fields with commas: a value containing a comma, quotation mark, or newline would need escaping.

  6. 03:28

    The default excel dialect uses commas and double quotes. If a source uses semicolons, specify delimiter=';' consistently when reading it. A dialect is a format agreement with the data source, not something to guess from one unusual row. The corrected parse_people function builds a DictReader, validates that the exact headers are name and city, and checks every row. A short row produces a None value; a long row produces a None key. Either is invalid under our contract. Blank values are also invalid. We then return a small dictionary for each person. The result preserves the embedded comma as part of one city string.

  7. 04:11

    This function is deliberately stricter than the underlying CSV parser because a business import should not quietly accept shifted or incomplete records. CSV dialects specify delimiter, quote character, escape behavior, and related choices. The default dialect is useful for many files, but external producers may choose a different format. Do not infer a semicolon dialect because one city happens to contain a comma; examine the actual file specification or known header. The writer is as important as the reader because an exported file can become the next program's input. The browser task returns parsed dictionaries, so it avoids file permission

  8. 04:54

    concerns and focuses on quoting and validation. For large files, process records as you iterate instead of materializing them all, but keep the same explicit checks. A malformed header is a contract failure before the first record is processed, while a malformed row is a record-level failure that can be reported with its line number in a production importer. The last run checks an embedded quotation mark and a header with no data rows. Both should work. Then try a missing city and an extra field; those should raise ValueError. Pause and connect this to the earlier JSON lesson: parsing a document's syntax does

  9. 05:34

    not validate the meaning of its fields. In the checked practice task, keep the header and row validation while replacing the naïve split. Finish the quiz, then explain why changing delimiter=';' on one side of a data exchange requires the other side to use the same dialect. The checked task implements parse_people(text) with csv.DictReader. Validate the exact header ['name', 'city']; use reader.fieldnames before iterating, then reject blank or whitespace-only names and cities and rows with missing or extra cells. Return a list of dictionaries with those two keys.

  10. 06:14

    Test an embedded comma, a quoted quotation mark, an empty dataset with only a header, and malformed input. A CSV parser solves quoting, but it does not validate your business rules; those remain explicit in the function. This separation is useful for imports, exports, and small data projects.

Understand the concept

A CSV comma is a field separator only when it appears outside a quoted field. The address New York, NY belongs to one cell when surrounded by quotes. A string split on commas cannot know this; it turns one address into two fields. The csv module understands the format and handles quoted fields, doubled quotation marks, alternate delimiters, and record boundaries. Use a file opened with newline='' or an io.StringIO stream, then pass the stream to csv.reader or csv.DictReader. The browser lesson uses an in-memory stream so you can run it immediately.

First run a naïve split on a record with an embedded comma and count the fields. Then read the same text with csv.reader. The latter returns two cells because the comma inside quotes is data. csv.DictReader uses the header row as field names, which makes a practical parser easier to read than positional indexes. A blank optional cell remains an empty string, not None. For this exercise, name and city are required and each record must have exactly those two columns. Raise ValueError for a malformed header or missing value rather than silently accepting a shifted row.

Writing CSV requires the same care. csv.writer quotes a field when needed and doubles embedded quote characters according to the dialect. Build a StringIO stream, write one row, and inspect the returned text. Do not construct output by joining fields with commas: a value containing a comma, quotation mark, or newline would need escaping. The default excel dialect uses commas and double quotes. If a source uses semicolons, specify delimiter=';' consistently when reading it. A dialect is a format agreement with the data source, not something to guess from one unusual row.

The checked task implements parse_people(text) with csv.DictReader. Validate the exact header ['name', 'city']; use reader.fieldnames before iterating, then reject blank or whitespace-only names and cities and rows with missing or extra cells. Return a list of dictionaries with those two keys. Test an embedded comma, a quoted quotation mark, an empty dataset with only a header, and malformed input. A CSV parser solves quoting, but it does not validate your business rules; those remain explicit in the function. This separation is useful for imports, exports, and small data projects.

  • Quoted commas stay inside one CSV field
  • csv.reader and DictReader parse format rules
  • csv.writer escapes output
  • Validate headers and required fields after parsing

See it step by step

Read the code, predict the output, then compare it with the result.

01. Keep a quoted comma inside one field

PYTHON
import csv, io
text = 'name,city\nAda,"New York, NY"\n'
print(list(csv.reader(io.StringIO(text))))
EXPECTED OUTPUT
[['name', 'city'], ['Ada', 'New York, NY']]

The quoted comma is data, so the second record still has exactly two cells.

02. Let the writer escape a quotation mark

PYTHON
import csv, io
stream = io.StringIO(newline='')
csv.writer(stream).writerow(['Ada "Ace"', 'Paris'])
print(stream.getvalue().strip())
EXPECTED OUTPUT
"Ada ""Ace""",Paris

The writer surrounds and doubles the quoted name according to the default dialect.

03. Read a semicolon dialect deliberately

PYTHON
import csv, io
text = 'name;city\nAda;Paris\n'
print(list(csv.DictReader(io.StringIO(text), delimiter=';')))
EXPECTED OUTPUT
[{'name': 'Ada', 'city': 'Paris'}]

An explicit delimiter matches the source format while keeping named columns.

A closer look

Follow the reasoning, inspect each result, then try the suggested changes in the console below.

01 / 03

Observe the quoted separator

A CSV comma is a field separator only when it appears outside a quoted field. The address New York, NY belongs to one cell when surrounded by quotes. A string split on commas cannot know this; it turns one address into two fields. The csv module understands the format and handles quoted fields, doubled quotation marks, alternate delimiters, and record boundaries. Use a file opened with newline='' or an io.StringIO stream, then pass the stream to csv.reader or csv.DictReader. The browser lesson uses an in-memory stream so you can run it immediately.

Predict how many cells a simple comma split creates for New York, NY, then compare that with csv.reader. This is the smallest counterexample to a homegrown parser. In a real file, pass the open file object rather than copying the whole file into a string.

PYTHON
import csv, io
text = 'name,city\nAda,"New York, NY"\n'
print(list(csv.reader(io.StringIO(text))))
EXPECTED OUTPUT
[['name', 'city'], ['Ada', 'New York, NY']]
Follow the reasoning
  1. Run the quoted-city example.
  2. Compare csv.reader with split(',').
02 / 03

Write instead of hand-joining

First run a naïve split on a record with an embedded comma and count the fields. Then read the same text with csv.reader. The latter returns two cells because the comma inside quotes is data. csv.DictReader uses the header row as field names, which makes a practical parser easier to read than positional indexes. A blank optional cell remains an empty string, not None. For this exercise, name and city are required and each record must have exactly those two columns. Raise ValueError for a malformed header or missing value rather than silently accepting a shifted row.

The writer knows when a field needs quotes. If the producer and consumer agree on another delimiter, give both the same dialect settings. A single embedded quote is enough to show why manual joins are fragile.

PYTHON
import csv, io
stream = io.StringIO(newline='')
csv.writer(stream).writerow(['Ada "Ace"', 'Paris'])
print(stream.getvalue().strip())
EXPECTED OUTPUT
"Ada ""Ace""",Paris
Follow the reasoning
  1. Write a value containing a quotation mark.
  2. Inspect how the quote is escaped.
03 / 03

Validate the parsed records

Writing CSV requires the same care. csv.writer quotes a field when needed and doubles embedded quote characters according to the dialect. Build a StringIO stream, write one row, and inspect the returned text. Do not construct output by joining fields with commas: a value containing a comma, quotation mark, or newline would need escaping. The default excel dialect uses commas and double quotes. If a source uses semicolons, specify delimiter=';' consistently when reading it. A dialect is a format agreement with the data source, not something to guess from one unusual row.

DictReader turns the header into keys. Check fieldnames first, then enforce required nonblank values. A missing cell becomes None and an extra cell is stored under a None key, so both cases should be rejected explicitly.

PYTHON
import csv, io
text = 'name;city\nAda;Paris\n'
print(list(csv.DictReader(io.StringIO(text), delimiter=';')))
EXPECTED OUTPUT
[{'name': 'Ada', 'city': 'Paris'}]
Follow the reasoning
  1. Read with DictReader.
  2. Reject missing and extra cells after parsing.

Try it in Python

Edit the example and run it. Python starts in your browser the first time you click Run.

Python console

Ready to run
Need input()? Add one value per line
Ctrl / ⌘ + Enter to run
OUTPUT
Your output appears here.
Your turnComplete parse_people(text) with csv.DictReader. Require exactly name,city headers and nonblank values. Return dictionaries with name and city. The prepared example should print [{'name': 'Ada', 'city': 'New York, NY'}]. Raise ValueError for malformed headers or rows.
Need a hint?

Use csv.DictReader(io.StringIO(text)), check reader.fieldnames, then reject None keys, missing cells, and values whose strip() is empty.

Complete the task and select Check task to verify your code.

    Quick quiz

    Three questions. You can change your answers and try again.

    01.Why does a quoted city containing a comma remain one CSV cell?

    02.What does csv.writer do to a field with an embedded quote?

    03.What should validate a required nonblank city after parsing?

    Typical mistakes

    Everyone meets these errors. See what causes them and how to fix them.

    Splitting a CSV row on commas

    COMMON MISTAKE
    line.split(',')
    FIX
    csv.reader(io.StringIO(line))

    What happens: A quoted address containing a comma becomes two fields.
    Use the parser that understands quoted separators.

    Joining fields to write CSV

    COMMON MISTAKE
    ','.join(row)
    FIX
    csv.writer(stream).writerow(row)

    What happens: Commas or quotes inside values corrupt the output.
    The writer applies the dialect's escaping rules.

    Browse this lesson’s problem fixes

    Sources and further reading