From 76395585bff9686da744e36631616262ba4840d4 Mon Sep 17 00:00:00 2001 From: devdavidejesus Date: Fri, 23 Oct 2026 21:36:27 -0300 Subject: [PATCH] gh-160556: Fix quadratic time in csv.Sniffer with long runs of spaces Sniffer._split_dormant() tried a match at every space of a run of spaces when the space was a candidate delimiter, and rescanned the rest of the run each time. A match starting at a space preceded by a space is never the leftmost one, so skip those positions with a lookbehind. The results do not change. The regression was introduced in gh-83273 (25fea6d64e1). --- Lib/csv.py | 7 +++++-- Lib/test/test_csv.py | 7 +++++++ .../Library/2026-10-24-00-36-27.gh-issue-160556.YTzSZI.rst | 2 ++ 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-24-00-36-27.gh-issue-160556.YTzSZI.rst diff --git a/Lib/csv.py b/Lib/csv.py index b406d82efffd9f3..18dfeb1db230b7f 100644 --- a/Lib/csv.py +++ b/Lib/csv.py @@ -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}])(?