Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Lib/csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,9 +437,12 @@ def _split_dormant(self, part, quotechar, combos):
while remaining:
# Include only the delimiters not found yet, so that the
# search skips over the found ones; the compiled patterns
# come from the re cache.
# come from the re cache. The lookbehind skips matches
# starting inside a run of spaces: an equivalent match
# starts at the beginning of the run, and rescanning the
# run from every space in it would take quadratic time.
cls = re.escape(''.join(sorted(remaining)))
m = re.compile(fr'(?:^|([\r\n{cls}]))'
m = re.compile(fr'(?:^|([\r\n{cls}])(?<! ))'
fr' *{quotechar}').search(part, pos)
if m is None:
break
Expand Down
7 changes: 7 additions & 0 deletions Lib/test/test_csv.py
Original file line number Diff line number Diff line change
Expand Up @@ -1631,6 +1631,13 @@ def test_sniff_single_column_quoted(self):
self.assertRaisesRegex(csv.Error, "Could not determine delimiter",
sniffer.sniff, sample)

def test_sniff_long_run_of_spaces(self):
# gh-160556: a long run of spaces not followed by a quote character
# used to take quadratic time.
sniffer = csv.Sniffer()
sample = ('a,b' + ' ' * 100_000 + 'c"\n') * 4
self.assertEqual(sniffer.sniff(sample).delimiter, ',')

def test_sniff_non_ascii_delimiter(self):
# gh-111820: an explicitly requested delimiter can be non-ASCII.
sniffer = csv.Sniffer()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix quadratic time in :meth:`csv.Sniffer.sniff` for samples with long runs
of spaces. The regression was introduced in 3.16.0a1.
Loading