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
Jump to a chapter
Read the video transcript
Select a timestamp to watch that moment on YouTube.
- 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.
- 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
- 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.
- 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
- 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.
- 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.
- 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
- 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
- 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.
- 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
import csv, io
text = 'name,city\nAda,"New York, NY"\n'
print(list(csv.reader(io.StringIO(text))))
[['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
import csv, io
stream = io.StringIO(newline='')
csv.writer(stream).writerow(['Ada "Ace"', 'Paris'])
print(stream.getvalue().strip())
"Ada ""Ace""",ParisThe writer surrounds and doubles the quoted name according to the default dialect.
03. Read a semicolon dialect deliberately
import csv, io
text = 'name;city\nAda;Paris\n'
print(list(csv.DictReader(io.StringIO(text), delimiter=';')))
[{'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.
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.
import csv, io
text = 'name,city\nAda,"New York, NY"\n'
print(list(csv.reader(io.StringIO(text))))
[['name', 'city'], ['Ada', 'New York, NY']]- Run the quoted-city example.
- Compare csv.reader with split(',').
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.
import csv, io
stream = io.StringIO(newline='')
csv.writer(stream).writerow(['Ada "Ace"', 'Paris'])
print(stream.getvalue().strip())
"Ada ""Ace""",Paris- Write a value containing a quotation mark.
- Inspect how the quote is escaped.
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.
import csv, io
text = 'name;city\nAda;Paris\n'
print(list(csv.DictReader(io.StringIO(text), delimiter=';')))
[{'name': 'Ada', 'city': 'Paris'}]- Read with DictReader.
- 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 runNeed input()? Add one value per line
Your output appears here.
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.
Typical mistakes
Everyone meets these errors. See what causes them and how to fix them.
Splitting a CSV row on commas
line.split(',')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
','.join(row)csv.writer(stream).writerow(row)What happens: Commas or quotes inside values corrupt the output.
The writer applies the dialect's escaping rules.