csv.QUOTE_STRINGS: Preserve Types in CSV Files

Published on: September 15, 2026
Reading time: 4 minutes
CSV data analysis with Python csv.QUOTE_STRINGS

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.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Code and file paths for Python PurePath.full_match
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    PurePath.full_match: Validate Paths with Glob Patterns

    Learn Python PurePath.full_match to validate complete paths with glob patterns, control case sensitivity, and build precise file filters.

    Ler mais

    Tempo de leitura: 4 minutos
    15/09/2026
    Asynchronous Python code representing asyncio.eager_task_factory
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    asyncio.eager_task_factory: Reduce Task Overhead

    Learn Python asyncio.eager_task_factory to reduce scheduling overhead, understand ordering changes, and optimize short coroutines safely.

    Ler mais

    Tempo de leitura: 4 minutos
    14/09/2026
    Developer working with UTC timestamps and Python calendar.timegm
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    calendar.timegm: Convert UTC to Unix Timestamps

    Learn Python calendar.timegm to convert UTC date tuples into Unix timestamps safely and avoid local-time conversion bugs.

    Ler mais

    Tempo de leitura: 5 minutos
    14/09/2026
    Programmer analyzing code to identify file MIME types with Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    mimetypes.guess_file_type: Detect MIME Types

    Learn Python mimetypes.guess_file_type for MIME detection in paths, URLs, uploads, and HTTP responses with safe fallbacks.

    Ler mais

    Tempo de leitura: 5 minutos
    13/09/2026
    Server components representing isolated Python interpreters running in parallel
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    InterpreterPoolExecutor: True Parallelism in Python

    Learn Python InterpreterPoolExecutor for CPU-bound tasks, isolated interpreters, true parallelism, and safer concurrency design.

    Ler mais

    Tempo de leitura: 6 minutos
    13/09/2026
    Microprocessor representing CPUs available to a Python process
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    os.process_cpu_count: Count Available CPUs

    Learn Python os.process_cpu_count to size workers according to the CPUs actually available to the process.

    Ler mais

    Tempo de leitura: 4 minutos
    12/09/2026