diff --git a/Lib/csv.py b/Lib/csv.py index b406d82efffd9f3..b93dcaefbde9f8c 100644 --- a/Lib/csv.py +++ b/Lib/csv.py @@ -599,7 +599,8 @@ def _detect_skipinitialspace(self, lines, delimiter, quotechar, counts = [[len(row) for row in rows] for rows in results] if counts[0] != counts[1]: # Prefer the more consistent row widths. - return len(set(counts[1])) <= len(set(counts[0])) + return (self._modal_share(counts[1])[1] >= + self._modal_share(counts[0])[1]) # Only some spaces are stripped. A field differs only if # a space was skipped at its start, which tells the padding # apart from the spaces inside quoted or escaped fields. diff --git a/Lib/test/test_csv.py b/Lib/test/test_csv.py index 056d7551cfb8e5f..58aa7a3e455ca56 100644 --- a/Lib/test/test_csv.py +++ b/Lib/test/test_csv.py @@ -1828,6 +1828,24 @@ def test_sniff_skipinitialspace_data(self): self.assertEqual(next(csv.reader(StringIO(sample), dialect)), ['a', ' b']) + def test_sniff_skipinitialspace_empty_field(self): + # Skipping the spaces loses the empty field, which makes the row + # widths less consistent, also when the sample has another short + # row or is cut off in the middle of the last row. + sniffer = csv.Sniffer() + sample = ('name age city\n' + 'alice 30 paris\n' + 'bob london\n' + 'carol 25 "new york"\n' + 'dave 40 berlin\n') + for last in ('erin 35\n', 'erin 35'): + with self.subTest(last=last): + dialect = sniffer.sniff(sample + last) + self.assertEqual(dialect.delimiter, ' ') + self.assertIs(dialect.skipinitialspace, False) + rows = list(csv.reader(StringIO(sample), dialect)) + self.assertEqual(rows[2], ['bob', '', 'london']) + def test_sniff_skipinitialspace_not_skipped(self): # A quoted or escaped space is not skipped, so it is not # an evidence of the padding. diff --git a/Misc/NEWS.d/next/Library/2026-11-05-09-52-26.gh-issue-161400.VevsID.rst b/Misc/NEWS.d/next/Library/2026-11-05-09-52-26.gh-issue-161400.VevsID.rst new file mode 100644 index 000000000000000..f3b0c4a3ce4e575 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-11-05-09-52-26.gh-issue-161400.VevsID.rst @@ -0,0 +1,3 @@ +Fix :meth:`csv.Sniffer.sniff` returning ``skipinitialspace=True`` +for a space-delimited sample with an unquoted empty field, +which made the sniffed dialect lose the field.