Quoting, escaping and embedded newlines

csv · Advanced · 11 min

Interoperating with databases and strict parsers often requires a specific quoting style.

The quoting parameter controls when the writer quotes fields, and how the reader interprets quotes:

  • QUOTE_MINIMAL (default): only fields containing special characters.
  • QUOTE_ALL: every field.
  • QUOTE_NONNUMERIC: all non-numeric fields; the reader converts unquoted fields to float.
  • QUOTE_NONE: never quote. The writer then needs an escapechar to escape delimiters, or it raises csv.Error.
  • QUOTE_STRINGS (3.12+): quote all strings; None is written as an empty, unquoted field. The reader turns unquoted empty fields into None and other unquoted fields into floats.
  • QUOTE_NOTNULL (3.12+): quote everything except None, which is written empty; the reader turns unquoted empty fields into None.

The last two finally let a CSV round trip distinguish None (,,) from an empty string (,"",), which databases care about.

Quote characters inside fields: with doublequote=True (default) a " is written as "". With doublequote=False, the writer uses escapechar instead (\").

Newlines inside fields are legal when quoted: the reader returns one record spanning several physical lines. Tools that process CSV line by line (wc -l, grep, naive splitting) miscount such files.

Example

import csv, io

def write(rows, **kw):
    buf = io.StringIO()
    csv.writer(buf, lineterminator="\n", **kw).writerows(rows)
    return buf.getvalue()

rows = [["id", "note", "score"], [1, "", None], [2, 'say "ok" now', 3.5]]
print(write(rows, quoting=csv.QUOTE_ALL), end="")
# "id","note","score" / "1","","" / "2","say ""ok"" now","3.5"
print(write(rows, quoting=csv.QUOTE_NOTNULL), end="")
# "id","note","score" / "1","", / "2","say ""ok"" now","3.5"
print(write([["a,b", 'q"']], quoting=csv.QUOTE_NONE, escapechar="\\"), end="")
# a\,b,q\"

back = csv.reader(io.StringIO('"x","",\n'), quoting=csv.QUOTE_NOTNULL)
print(next(back))                                    # ['x', '', None]

Remember

  • QUOTE_MINIMAL, ALL, NONNUMERIC, NONE, STRINGS, NOTNULL control quoting
  • QUOTE_NOTNULL / QUOTE_STRINGS (3.12) distinguish None from empty strings
  • QUOTE_NONE needs an escapechar for special characters

Watch out for

  • Counting CSV records with wc -l when quoted fields contain newlines.

Check yourself

  1. What distinguishes QUOTE_NOTNULL from QUOTE_ALL when writing None?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.python.org/3/library/csv.html#csv.QUOTE_NOTNULL