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.
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:
crew,club,seconds
Harbor Herons,"Riverside, North",83
"The ""Fast"" Four",Lakeside,79line.split(",") on the second line gives four pieces instead of three, and leaves the quotes in place. The csv module parses it correctly:
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:
for row in csv.DictReader(file):
seconds = int(row["seconds"]) # every value arrives as a stringCode 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 handleValueErrorfor bad cells. - Missing and extra fields don't raise errors. If a row is short, the missing keys get the value
None(orrestval); if it has extra fields, they're collected in a list under the keyNone(orrestkey). 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:
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 thecsvmodule 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 recommendsnewline=""for both reading and writing.encodingmust 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"; aUnicodeDecodeErroror 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.
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.
CSV Handling in Depth in a small program
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)Harbor Herons 85
Canal Otters 81Line-by-line explanation
- Line 3
StringIOstands in for a file containing a header and two rows. - Line 4
DictReaderturns each row into a dictionary keyed by the header names. - Line 5
secondsarrives as text, so it's converted withint()before adding the 2-second penalty.
Validating and exporting results
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")crew,club,seconds
Harbor Herons,"Riverside, North",85
Mill Swifts,Weir Club,93
line 3: no time for Canal OttersLine-by-line explanation
- Line 5The quoted club name contains a comma but is still one field.
- Line 11Counting from 2 matches the file's line numbers, because line 1 is the header.
- Line 19
DictWriterre-quotes the club name automatically when writing it out.
Common CSV Handling in Depth Mistakes
Splitting CSV lines on commas
for line in open("results.csv"):
crew, club, seconds = line.strip().split(",") # breaks on "Riverside, North"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
with open("out.csv", "w") as f:
csv.writer(f).writerows(rows) # blank lines between rows on Windowswith 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.
What type is a numeric field returned by DictReader?
Which review concern is valid for csv handling in depth?
CSV Handling in Depth Quiz
2 questions to check what you learned.
What type is a numeric field returned by DictReader?
- int
- str
- Python ignores the distinction
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