Skip to content

Commit 7c9f890

Browse files
gh-83273: Rewrite csv.Sniffer dialect detection using trial parsing
Guess the dialect by parsing the sample with every plausible combination of delimiter, quotechar and escapechar, using the actual CSV parser in strict mode, and choosing the combination which splits the sample into rows with the most consistent number of fields. The old heuristics, which guessed the delimiter from characters adjacent to quotes and from character frequencies, are removed. A large sample is parsed incrementally: first only its beginning, then, after eliminating the combinations which are clearly worse than the leader, a several times larger part, and so on. * csv.Sniffer can now detect escapechar='\\'. * Explicitly requested delimiters are no longer restricted to ASCII. * A delimiter inside a quoted field no longer wins over the actual delimiter, and sniffing no longer takes quadratic time on quoted samples. * The sample can be cut off at an arbitrary point: in the middle of a row, of a quoted field or of an escaped sequence. * Only '\r', '\n' and '\r\n' are treated as row separators, so characters like '\x1c' can now be detected as a delimiter. * A preamble (title or comment lines) before the data does not prevent detection if the data rows outnumber the preamble lines. * A sample consisting of a single column of quoted fields now raises csv.Error instead of guessing a delimiter from the content of the fields, and Sniffer.has_header() no longer raises ValueError for such samples. * doublequote is detected by comparing the two readings of the sample. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 1fece44 commit 7c9f890

5 files changed

Lines changed: 550 additions & 180 deletions

File tree

Doc/library/csv.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,22 @@ The :mod:`!csv` module defines the following classes:
312312
is given, it is interpreted as a string containing possible valid
313313
delimiter characters.
314314

315+
The dialect is deduced by parsing the sample with every plausible
316+
combination of parameters
317+
and choosing the combination which splits the sample into rows
318+
with the most consistent number of fields,
319+
so the returned dialect is consistent with how :func:`reader`
320+
will parse the sample.
321+
Raise :exc:`Error` if no combination fits the sample,
322+
in particular if it is a single column,
323+
so there is no delimiter to find.
324+
325+
.. versionchanged:: next
326+
The dialect is now deduced by trial parsing
327+
and the results may differ from those of earlier Python versions.
328+
The *escapechar* parameter can now be detected,
329+
and the requested *delimiters* are not restricted to ASCII.
330+
315331

316332
.. method:: has_header(sample)
317333

Doc/whatsnew/3.16.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,21 @@ codecs
9696
even when a built-in codec of the same name exists.
9797
(Contributed by Serhiy Storchaka in :gh:`152997`.)
9898

99+
csv
100+
---
101+
102+
* :meth:`csv.Sniffer.sniff` now deduces the dialect by trial parsing
103+
instead of heuristics based on matching isolated fragments
104+
and on character frequencies,
105+
so the result is consistent with how :func:`csv.reader` will parse the sample.
106+
It can now detect the *escapechar* parameter,
107+
accepts non-ASCII delimiters in the *delimiters* argument,
108+
no longer misdetects the delimiter
109+
when the sample contains delimiter characters inside quoted fields,
110+
and no longer takes quadratic time on quoted samples.
111+
The results may differ from those of earlier Python versions.
112+
(Contributed by Serhiy Storchaka in :gh:`83273`.)
113+
99114
curses
100115
------
101116

0 commit comments

Comments
 (0)