From 7a42a3a850ea6eb9fb5db92261b5e4d626d13a9d Mon Sep 17 00:00:00 2001 From: yap Date: Mon, 24 Aug 2026 11:36:56 +0800 Subject: [PATCH] gh-156185: Ignore all ASCII whitespace in re \p{} property names The docs promise property and value names are matched loosely (whitespace not significant), but _normalize() only removed spaces. Now tab/newline/cr/formfeed/vertical-tab are ignored as well. --- Lib/re/_properties.py | 8 ++++---- Lib/test/test_re.py | 6 ++++++ .../2026-08-24-10-30-00.gh-issue-156185.a1b2c3.rst | 3 +++ 3 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-24-10-30-00.gh-issue-156185.a1b2c3.rst diff --git a/Lib/re/_properties.py b/Lib/re/_properties.py index 6310aa7fa88f955..09f03c05a88eaf2 100644 --- a/Lib/re/_properties.py +++ b/Lib/re/_properties.py @@ -209,11 +209,11 @@ def _analytic_ranges(): def _normalize(name): - # Unicode property and value names are matched loosely: case, spaces, - # hyphens and underscores are not significant, and an initial "is" prefix - # is ignored (UAX #44 5.9, "Matching Rules", UAX44-LM3; + # Unicode property and value names are matched loosely: case, whitespace + # and hyphens and underscores are not significant, and an initial "is" + # prefix is ignored (UAX #44 5.9, "Matching Rules", UAX44-LM3; # https://www.unicode.org/reports/tr44/). - name = name.lower().replace("_", "").replace("-", "").replace(" ", "") + name = "".join(ch for ch in name.lower() if ch not in " \t\n\r\f\v_-") # Strip a leading "is", unless "is" is the whole name and so not a prefix # (e.g. the Line_Break value lb=IS). if name != "is": diff --git a/Lib/test/test_re.py b/Lib/test/test_re.py index d086fd521e73b59..f7d89f652ec2bc6 100644 --- a/Lib/test/test_re.py +++ b/Lib/test/test_re.py @@ -929,6 +929,12 @@ def test_property_escapes(self): self.assertTrue(re.fullmatch(r'\p{General_Category=Lu}+', 'ABC')) self.assertTrue(re.fullmatch(r'\p{ lu }+', 'ABC')) self.assertTrue(re.fullmatch(r'\p{LU}+', 'ABC')) + # All ASCII whitespace is ignored, not just spaces (UAX44-LM3; + # gh-156185). + for ws in " \t\n\r\f\v": + with self.subTest(ws=ws): + self.assertTrue(re.fullmatch(r'\p{L%su}+' % ws, 'ABC')) + self.assertTrue(re.fullmatch(r'\p{gc%s=L%su}+' % (ws, ws), 'ABC')) # An initial "is" prefix is ignored (UAX44-LM3), on the property name # and on a gc value; "is" alone is not a prefix (cf. lb=IS). self.assertTrue(re.fullmatch(r'\p{isLu}+', 'ABC')) diff --git a/Misc/NEWS.d/next/Library/2026-08-24-10-30-00.gh-issue-156185.a1b2c3.rst b/Misc/NEWS.d/next/Library/2026-08-24-10-30-00.gh-issue-156185.a1b2c3.rst new file mode 100644 index 000000000000000..60a2b10f4c9d2cc --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-24-10-30-00.gh-issue-156185.a1b2c3.rst @@ -0,0 +1,3 @@ +``\p{property}`` and ``\p{property=value}`` regex escapes now ignore all +ASCII whitespace in property and value names, as documented, instead of +only spaces.