csv.QUOTE_STRINGS is a quoting mode in Python’s csv module designed to make the distinction between text and non-text values more explicit. When writing, string fields are quoted while non-string values can remain unquoted. When reading, unquoted fields may be interpreted similarly to QUOTE_NONNUMERIC, while quoted values remain strings. This is useful when a CSV file must preserve the difference between text, numbers, empty strings, and missing values without a custom serialization layer.
This guide explains how to use csv.QUOTE_STRINGS, how it compares with other quoting modes, and which edge cases matter in production integrations.
Why QUOTE_STRINGS matters
CSV has no universal type system. The value 42 may be a number, a product code, an account identifier, or text that must keep leading zeros. Consumers often infer types from syntax, which can silently alter data.
import csv
rows = [
["product", "quantity", "price"],
["Python Course", 2, 149.9],
]
with open("sales.csv", "w", newline="", encoding="utf-8") as file:
writer = csv.writer(file, quoting=csv.QUOTE_STRINGS)
writer.writerows(rows)
Strings are written with quotes while numeric values remain unquoted. This gives downstream systems a clearer signal about the producer’s intended types.
Comparison with other quoting modes
QUOTE_MINIMAL quotes only fields that contain special characters. QUOTE_ALL quotes every field. QUOTE_NONE avoids quotes and usually requires an escape character. QUOTE_NONNUMERIC quotes nonnumeric values and converts unquoted values to float during reading.
QUOTE_STRINGS focuses specifically on Python strings. It is a good fit when your domain distinguishes text from numbers and you want that distinction visible in the generated file.
Using DictWriter
import csv
records = [
{"name": "Alice", "age": 29, "balance": 1250.50},
{"name": "Bruno", "age": 34, "balance": 980.00},
]
with open("customers.csv", "w", newline="", encoding="utf-8") as file:
writer = csv.DictWriter(
file,
fieldnames=["name", "age", "balance"],
quoting=csv.QUOTE_STRINGS,
)
writer.writeheader()
writer.writerows(records)
This pattern works well for API exports, administrative reports, catalogs, billing data, and service-to-service file exchanges.
Reading and type conversion
When reading, the file must follow a consistent contract. Quoted fields remain strings, while unquoted fields may be interpreted numerically. Do not treat implicit conversion as complete validation.
import csv
with open("customers.csv", newline="", encoding="utf-8") as file:
reader = csv.reader(file, quoting=csv.QUOTE_STRINGS)
for row in reader:
print(row)
Validate column count, required fields, ranges, formats, and domain constraints after parsing. A syntactically valid number can still be invalid for your application.
Numeric-looking strings
Identifiers such as ZIP codes, order numbers, phone numbers, SKUs, and account codes often look numeric but must remain text.
rows = [
["code", "quantity"],
["000127", 4],
]
Because "000127" is a string, it is quoted. Consumers that respect the contract are less likely to turn it into 127.
None and empty fields
Null handling must be documented. Depending on the Python version and quoting mode behavior, None may be written as an empty unquoted field while an empty string is quoted. This can allow a reader to distinguish missing data from an intentionally empty string.
row = [None, "", "text", 0]
Test this behavior with the exact Python versions used in development, CI, and production. External spreadsheet software may not preserve the distinction.
Custom delimiters and dialects
You can combine the mode with semicolon-separated files, custom line endings, and other dialect settings.
with open("data.csv", "w", newline="", encoding="utf-8") as file:
writer = csv.writer(
file,
delimiter=";",
quoting=csv.QUOTE_STRINGS,
lineterminator="\n",
)
writer.writerow(["item", "value"])
writer.writerow(["Subscription", 99.9])
Explicitly define delimiter, encoding, and line ending when another system consumes the file. This avoids differences between operating systems, Excel configurations, and BI tools.
Normalize values before writing
Dates, decimals, enums, and custom objects have no universal CSV representation.
from datetime import date
from decimal import Decimal
def serialize(value):
if isinstance(value, Decimal):
return format(value, "f")
if isinstance(value, date):
return value.isoformat()
return value
Remember that converting a decimal to a string means it will be quoted. That may preserve precision, but it may also make the consumer treat the value as text. Define the contract before exporting.
Round-trip testing
A reliable test writes a row, reads it again, and compares both values and types.
import csv
import io
def roundtrip(row):
buffer = io.StringIO(newline="")
writer = csv.writer(buffer, quoting=csv.QUOTE_STRINGS)
writer.writerow(row)
buffer.seek(0)
return next(csv.reader(buffer, quoting=csv.QUOTE_STRINGS))
Include commas, quotes, line breaks, Unicode, None, empty strings, negative values, scientific notation, and leading-zero identifiers.
Spreadsheet risks
Spreadsheet programs may remove leading zeros, convert long identifiers to scientific notation, or reinterpret dates. They can also execute formulas from cells beginning with characters such as =, +, -, or @. If files will be opened in spreadsheet software, evaluate CSV injection risk and sanitize according to your security policy.
Using the mode in file pipelines
For file discovery, read Python pathlib.Path.walk. To inspect file types, see Python mimetypes.guess_file_type. For worker pipelines, see Python queue.SimpleQueue. For clearer data models, see Python dataclasses.KW_ONLY.
Version compatibility
csv.QUOTE_STRINGS is a relatively recent feature. Confirm the minimum Python version supported by your project. If older versions are required, create a compatibility layer that preprocesses fields or selects another quoting mode.
The official Python csv documentation is the primary reference. For general CSV conventions, consult RFC 4180.
Operational best practices
Document delimiter, encoding, null representation, quoting behavior, and type rules. Keep sample files under version control. Reject unexpected columns when the contract is strict. Log parse failures with line numbers without exposing sensitive data.
When importing untrusted CSV files, enforce limits for file size, row count, column count, and maximum field length. Validate values after parsing and avoid assuming that quotes make content safe.
Conclusion
csv.QUOTE_STRINGS gives CSV producers a direct way to quote text while leaving non-string values unquoted. It can preserve leading-zero identifiers, make type intent visible, and improve interoperability when both sides share the same rules.
Use it with explicit schemas, round-trip tests, documented null semantics, and careful spreadsheet handling. CSV remains a text format, so reliable exchange depends on a clear agreement between producer and consumer.







