gh-83273: Rewrite csv.Sniffer dialect detection using trial parsing#153694
Open
serhiy-storchaka wants to merge 1 commit into
Open
gh-83273: Rewrite csv.Sniffer dialect detection using trial parsing#153694serhiy-storchaka wants to merge 1 commit into
serhiy-storchaka wants to merge 1 commit into
Conversation
…sing 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>
Documentation build overview
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
csv.Sniffer.sniff()now guesses 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 result is consistent with howcsv.reader()will parse the sample — by construction.The old heuristics are removed. They guessed the delimiter from characters adjacent to quotes (
_guess_quote_and_delimiter) and from character frequency statistics (_guess_delimiter), so a delimiter inside a quoted field could win over the actual one, sniffing took quadratic time on ordinary quoted data (20+ seconds on a ~1 MB sample, which looked like a hang), and an escape character could not be detected at all.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. Combinations with a quotechar are not parsed until the quote character occurs at the start of a field. The verdict that a delimiter does not delimit anything is not trusted to the first part of the sample, which covers its least representative beginning (titles, comments, headers).
User-visible changes
csv.Sniffercan now detectescapechar='\\'.'\r','\n'and'\r\n'are treated as row separators, matching the reader; characters like'\x1c'can now be detected as a delimiter.csv.Errorinstead of guessing a delimiter from the content of the fields, andSniffer.has_header()no longer raisesValueErrorfor such samples.doublequoteis detected by comparing the two readings of the sample.Results may differ from earlier Python versions on valid samples; this is documented with a
versionchangednote and a What's New entry.Performance
Release build,
sniff()wall time:Error)The old regular expressions were quadratic on quoted data; the new code is linear in the sample size with a small factor.
Closed issues
delimitersto list of eligible delimiters #111820: a non-ASCII delimiter in the delimiters argument was ignored.'\r\n'broke detection (the delimiter was detected as a letter).Also addressed, at the triagers' discretion:
sniff("''")now raisescsv.Error, sohas_header()cannot leakValueError.Supersedes the stalled PRs gh-109639, gh-103926, gh-153635 and gh-27256, which patch the removed regular expressions. gh-109639 and gh-153635 remain valid as narrow backport candidates for the maintenance branches.
Notes for reviewers
quoting(Feature request: sniff csv quoting and lineterminator #129374) andlineterminator(csv.Sniffer does not detect lineterminator #75008) is deliberately out of scope; the new structure has a natural place for both (_detect_*methods on the winning combination).