Repository navigation
csv writer needs more quoting rules #67230
Description
Activity
The csv module currently implements four quoting rules for dialects: QUOTE_MINIMAL, QUOTE_ALL, QUOTE_NONNUMERIC and QUOTE_NONE. These rules treat values of None the same as an empty string, i.e. by outputting two consecutive quotes. I propose the addition of two new rules, QUOTE_NOTNULL and QUOTE_STRINGS. The former behaves like QUOTE_ALL while the later behaves like QUOTE_NONNUMERIC, except that in both cases values of None are output as an empty field. Examples follow.
Current behavior (which will remain unchanged)
>>> csv.register_dialect('quote_all', quoting=csv.QUOTE_ALL) >>> csv.writer(sys.stdout, dialect='quote_all').writerow(['foo', None, 42]) "foo","","42" >>> csv.register_dialect('quote_nonnumeric', quoting=csv.QUOTE_NONNUMERIC) >>> csv.writer(sys.stdout, dialect='quote_nonnumeric').writerow(['foo', None, 42]) "foo","",42
Proposed behavior
>>> csv.register_dialect('quote_notnull', quoting=csv.QUOTE_NOTNULL) >>> csv.writer(sys.stdout, dialect='quote_notnull').writerow(['foo', None, 42]) "foo",,"42" >>> csv.register_dialect('quote_strings', quoting=csv.QUOTE_STRINGS) >>> csv.writer(sys.stdout, dialect='quote_strings').writerow(['foo', None, 42]) "foo",,42
- addedstdlibStandard Library Python modules in the Lib/ directoryStandard Library Python modules in the Lib/ directorytype-featureA feature request or enhancementA feature request or enhancement
on Dec 12, 2014 As an enhancement, this could be added only to 3.5. The proposal sounds reasonable to me.
David: That's not a problem for me.
Sorry I can't provide real patches, but I'm not in a position to compile (much less test) the C implementation of _csv. I've looked at the code online and below are the changes that I think need to be made. My use cases don't require special handing when reading empty fields, so the only changes I've made are to the code for writers. I did verify that the reader code mostly only checks for QUOTE_NOTNULL when parsing. This means that completely empty fields will continue to load as zero-length strings, not None. I won't stand in the way of anyone wanting to "fix" that for these new rules.
typedef enum {
QUOTE_MINIMAL, QUOTE_ALL, QUOTE_NONNUMERIC, QUOTE_NONE,
QUOTE_STRINGS, QUOTE_NOTNULL
} QuoteStyle;static StyleDesc quote_styles[] = {
{ QUOTE_MINIMAL, "QUOTE_MINIMAL" },
{ QUOTE_ALL, "QUOTE_ALL" },
{ QUOTE_NONNUMERIC, "QUOTE_NONNUMERIC" },
{ QUOTE_NONE, "QUOTE_NONE" },
{ QUOTE_STRINGS, "QUOTE_STRINGS" },
{ QUOTE_NOTNULL, "QUOTE_NOTNULL" },
{ 0 }
};switch (dialect->quoting) { case QUOTE_NONNUMERIC: quoted = !PyNumber_Check(field); break; case QUOTE_ALL: quoted = 1; break; case QUOTE_STRINGS: quoted = PyString_Check(field); break; case QUOTE_NOTNULL: quoted = field != Py_None; break; default: quoted = 0; break; }
" csv.QUOTE_MINIMAL means only when required, for example, when a\n"
" field contains either the quotechar or the delimiter\n"
" csv.QUOTE_ALL means that quotes are always placed around fields.\n"
" csv.QUOTE_NONNUMERIC means that quotes are always placed around\n"
" fields which do not parse as integers or floating point\n"
" numbers.\n"
" csv.QUOTE_STRINGS means that quotes are always placed around\n"
" fields which are strings. Note that the Python value None\n"
" is not a string.\n"
" csv.QUOTE_NOTNULL means that quotes are only placed around fields\n"
" that are not the Python value None.\n"Samwyse, are these suggestions just based on ideas of what could be done or have you encountered real-world CSV data exchanges that couldn't be handled by the CSV module?
It doesn't look like a difficult change, but is it really needed? I guess my reaction is the same as Raymond's. Are there real-world uses where the current set of quoting styles isn't sufficient?
Used function PyUnicode_Check instead of PyString_Check
Yes, it's based on a real-world need. I work for a Fortune 500 company and we have an internal tool that exports CSV files using what I've described as the QUOTE_NOTNULL rules. I need to create similar files for re-importation. Right now, I have to post-process the output of my Python program to get it right. I added in the QUOTE_STRINGS rule for completeness. I think these two new rules would be useful for anyone wanting to create sparse CSV files.
If I understand correctly, your software needs to distinguish between
# wrote ["foo", "", 42, None] with quote_all in effect
"foo","","42",""and
# wrote ["foo", None, 42, ""] with quote_nonnull in effect
"foo",,"42",""so you in effect want to transmit some type information through a CSV file?
Skip, I don't have any visibility into how the Java program I'm feeding data into works, I'm just trying to replicate the csv files that it exports as accurately as possible. It has several other quirks, but I can replicate all of them using Dialects; this is the only "feature" I can't. The files I'm looking at have quoted strings and numbers, but there aren't any quoted empty strings. I'm using a DictWriter to create similar csv files, where missing keys are treated as values of None, so I'd like those printed without quotes. If we also want to print empty strings without quotes, that wouldn't impact me at all.
Besides my selfish needs, this could be useful to anyone wanting to reduce the save of csv files that have lots of empty fields, but wants to quote all non-empty values. This may be an empty set, I don't know.
Thanks for the update berker.peksag. I'm still not convinced that the csv module should be modified just so one user (sorry samwyse) can match the input format of someone's Java program. It seems a bit like trying to make the csv module type-sensitive. What happens when someone finds a csv file containing timestamps in a format other than the datetime.datetime object will produce by default? Why is None special as an object where bool(obj) is False?
I think the better course here is to either:
-
subclass csv.DictWriter, use dictionaries as your element type, and have its writerow method do the application-specific work.
-
define a writerow() function which does something similar (essentially wrapping csv.writerow()).
If someone else thinks this is something which belongs in Python's csv module, feel free to reopen and assign it to yourself.
-
The csv module is already type-sensitive (with QUOTE_NONNUMERIC). I agree, that we shouldn't modify the csv module just for one user and one program.
If a standard CVS library in Java (or other popular laguages) differentiates between empty string and null value when read from CSV, it would be a serious argument to support this in Python. Quick search don't find this.
71 remaining items
I'll try to summarise the state of this issue:
The proposed feature was to add more quoting rules to the
csvwriter. This has been implemented (with docs and tests)1. @serhiy-storchaka requested in #67230 (comment) that the issue be held open:Wait, #29469 only changes writer, not reader. There are no tests for reader with new quoting styles, so we cannot be sure that nothing is broken. [...]
@samwyse remarked in #67230 (comment):
[..] I can't really see an easy way [for csv reader] to distinguish between «,,» and «,"",». So unless someone more knowledgeable than I thinks that it's simple, I propose that we punt this down the road, making the two new quoting rules illegal for reader objects for the time being. [...]
Thinking more about the problem, I think it's wrong to do anything to the reader. In its entire history, the reader has never done any sort of type inference. That would be exactly what distinguishing an empty field from a quoted empty field would be, a (small) bit of type inference. [...]
The better course at this point would be to adjust the docs to state that the two new quoting rules have no effect in the reader. [...]
Would someone like to work on a PEP regarding type conversion in the reader?
I suggest to close this as completed (the csv writer feature was implemented) and to open up a new issue regarding what to do with the csv reader.
Footnotes
-
we should amend the issue title to reflect that the feature request was for the csv writer only ↩
-
- changed the title
[-]csv needs more quoting rules[/-][+]csv writer needs more quoting rules[/+]on Jan 5, 2024 the reader has never done any sort of type inference.
But it does.
>>> next(csv.reader(['123,"123"'])) ['123', '123'] >>> next(csv.reader(['123,"123"'], quoting=csv.QUOTE_NONNUMERIC)) [123.0, '123']If quoting style is QUOTE_NONNUMERIC, it returns non-quoted field as numeric. I expect the same for None.
As I already wrote, I suggest to summarise the needed reader changes in a new issue.
Reacted by Skip Montanarothe reader has never done any sort of type inference.
But it does.
>>> next(csv.reader(['123,"123"'])) ['123', '123'] >>> next(csv.reader(['123,"123"'], quoting=csv.QUOTE_NONNUMERIC)) [123.0, '123']If quoting style is QUOTE_NONNUMERIC, it returns non-quoted field as numeric. I expect the same for None.
That's not really inferring the type in my book. It's effectively being told by the programmer that unquoted numeric strings are to be returned as numbers. If it was inferring the type, your first example would also spit out a float as the first value.
Reacted by Erlend E. AaslandRight, and I suggest that QUOTE_NOTNULL is told by the programmer that unquoted empty strings are to be returned as None.
Right, and I suggest that QUOTE_NOTNULL is told by the programmer that unquoted empty strings are to be returned as None.
Can you please suggest this csv reader change in a new issue?
It is perhaps too late to make this change in 3.12, so we can open a new issue.
Reacted by Erlend E. AaslandOpened #113732.
Reacted by Erlend E. Aasland- added a commit that references this issue
on Feb 1, 2024 - added a commit that references this issue
on Feb 1, 2024 - added a commit that references this issue
on Feb 11, 2024
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsDone
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
Linked PRs