How to Write CSV Values With Commas, Quotes and Newlines in Python
A CSV built with ','.join(row) breaks as soon as a value contains a comma, a double quote or a line break - Smith, Bob becomes two columns. CSV has quoting rules for these cases, and Python's built-in csv module applies them for you. Write rows with csv.writer into a file or an io.StringIO, and every cell comes back exactly as it went in.
The bug
Here is a hand-written CSV export:
def export_csv(rows: list[list[str]]) -> str:
out = ""
for row in rows:
out += ",".join(row) + "\n"
return out
It works for clean data. Then a customer name contains a comma:
import csv, io
text = export_csv([["1", "Smith, Bob"]])
print(list(csv.reader(io.StringIO(text))))
# [['1', 'Smith', ' Bob']] - 3 cells instead of 2
Any program that reads this file - Excel, pandas, a database import - sees an extra column. A double quote or a multi-line address breaks the row in other ways.
How CSV quoting works
The common CSV format (described in RFC 4180) has three rules:
- A cell that contains a comma, a double quote or a line break is wrapped in double quotes.
- A double quote inside a quoted cell is written twice:
"becomes"". - A line break inside a quoted cell is part of the value, not the end of the row.
So these values:
| Value | Written as |
|---|---|
Smith, Bob |
"Smith, Bob" |
O"Reilly |
"O""Reilly" |
12 Main St + line break + Apt 4 |
"12 Main St (line break) Apt 4" |
You could write these rules yourself, but it is easy to miss a case. The standard library already does it.
The fix: csv.writer
import csv
import io
def export_csv(rows: list[list[str]]) -> str:
buf = io.StringIO()
csv.writer(buf).writerows(rows)
return buf.getvalue()
Check it with the same round-trip test:
rows = [
["id", "name", "address"],
["1", "Smith, Bob", "12 Main St"],
["2", 'O"Reilly', "12 Main St\nApt 4"],
]
text = export_csv(rows)
assert list(csv.reader(io.StringIO(text))) == rows
The writer quotes only the cells that need it, doubles inner quotes, and keeps line breaks inside quoted cells. The reader turns the text back into the original values.
Writing to a file
When you write to a file, open it with newline="":
with open("customers.csv", "w", newline="", encoding="utf-8") as f:
writer = csv.writer(f)
writer.writerow(["id", "name"])
writer.writerows(data_rows)
csv.writer ends each row with \r\n by default. Without newline="", Python
may change the line endings again on some systems, and you get empty lines
between rows when the file is opened in Excel.
Writing dictionaries with DictWriter
If your rows are dicts (for example from a database cursor), csv.DictWriter
maps keys to columns:
fields = ["id", "name", "email"]
writer = csv.DictWriter(buf, fieldnames=fields, extrasaction="ignore")
writer.writeheader()
writer.writerows(customers) # list of dicts
extrasaction="ignore" drops keys that are not in fields instead of raising
an error.
Returning CSV from a Flask endpoint
from flask import Response
@app.get("/export")
def export():
text = export_csv(load_rows())
return Response(
text,
mimetype="text/csv",
headers={"Content-Disposition": "attachment; filename=export.csv"},
)
The Content-Disposition header tells the browser to download the file.
Other options you may need
quoting=csv.QUOTE_ALLwraps every cell in quotes. Some import tools expect this.delimiter=";"writes semicolon-separated files, common in regions where the comma is the decimal separator.encoding="utf-8-sig"adds a byte order mark so Excel detects UTF-8 and shows accented characters correctly.- Spreadsheet formulas: a cell that starts with
=,+,-or@can run as a formula when the file is opened in a spreadsheet. If users control the data, prefix those cells with a single quote (') before writing.
The general lesson
CSV looks simple, but its edge cases are in the data, not in your test file with clean names. When a format has a parser and a writer in the standard library, use them, and test with values that contain the separator, the quote character and a line break.
Do this project yourself. It's ready in a live cloud workspace in your browser - when you finish, it lands on your portfolio.
Start this project - freeWhat you'll practice
- Seeing how ",".join() corrupts rows that contain commas, quotes or newlines
- Replacing hand-built CSV strings with csv.writer and io.StringIO
- Writing a round-trip test with csv.reader to prove every cell survives
- Opening CSV files with newline="" and returning CSV from a Flask endpoint
FAQ
How do I write a value with a comma to a CSV file in Python?
Use csv.writer. It wraps any cell that contains a comma in double quotes, so "Smith, Bob" stays one cell. Do not build rows with ",".join().
How do you escape a double quote in CSV?
Write it twice inside a quoted cell. The value O"Reilly is written as "O""Reilly". Python's csv module does this for you.
Why does my CSV file have blank lines between rows?
The file was opened without newline="". csv.writer writes \r\n line endings, and the file object changes the line endings again. Open the file with open(path, "w", newline="").
How do I write a CSV to a string instead of a file in Python?
Create an io.StringIO(), pass it to csv.writer(), write the rows, and call getvalue() on the StringIO to get the text.
Keep learning
Do this project yourself. It's ready in a live cloud workspace in your browser - when you finish, it lands on your portfolio.
Start this project - free