-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
gh-133546: Make re.Match
a well-rounded Sequence
type
#133549
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
74480a7
a3de846
603b1d1
70b73e4
f51ef45
f218828
5272141
d0aa6fa
5f67be0
17feaa6
fe709f8
4095b52
51d918d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -2,12 +2,14 @@ | |||
cpython_only, captured_stdout, | ||||
check_disallow_instantiation, linked_to_musl, | ||||
warnings_helper, SHORT_TIMEOUT, Stopwatch, requires_resource) | ||||
import itertools | ||||
import locale | ||||
import re | ||||
import string | ||||
import sys | ||||
import unittest | ||||
import warnings | ||||
from collections.abc import Sequence | ||||
from re import Scanner | ||||
from weakref import proxy | ||||
|
||||
|
@@ -570,10 +572,14 @@ def test_match_getitem(self): | |||
self.assertEqual(m[1], 'a') | ||||
self.assertEqual(m[2], None) | ||||
self.assertEqual(m[3], None) | ||||
self.assertEqual(m[-1], None) | ||||
self.assertEqual(m[-2], None) | ||||
self.assertEqual(m[-3], 'a') | ||||
self.assertEqual(m[-4], 'a') | ||||
with self.assertRaisesRegex(IndexError, 'no such group'): | ||||
m['X'] | ||||
with self.assertRaisesRegex(IndexError, 'no such group'): | ||||
m[-1] | ||||
m[-5] | ||||
with self.assertRaisesRegex(IndexError, 'no such group'): | ||||
m[4] | ||||
with self.assertRaisesRegex(IndexError, 'no such group'): | ||||
|
@@ -594,13 +600,144 @@ def test_match_getitem(self): | |||
self.assertEqual(m[1], 'a') | ||||
self.assertEqual(m[2], None) | ||||
self.assertEqual(m[3], 'c') | ||||
self.assertEqual(m[-1], 'c') | ||||
self.assertEqual(m[-2], None) | ||||
self.assertEqual(m[-3], 'a') | ||||
self.assertEqual(m[-4], 'ac') | ||||
|
||||
# Cannot assign. | ||||
with self.assertRaises(TypeError): | ||||
m[0] = 1 | ||||
|
||||
# No len(). | ||||
self.assertRaises(TypeError, len, m) | ||||
def test_match_getitem_slice(self): | ||||
m = re.match(r"(a)(b)(c)", "abc") | ||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can compactify the test as follows (don't forget to import itertools): m = re.match(r"(a)(b)(c)", "abc")
seq = ["abc", "a", "b", "c"]
for start, end, step in itertools.product(
[None, *range(-len(seq), len(seq) + 1)],
repeat=3,
):
with self.subTest(start=start, end=end, step=step):
self.assertEqual(m[start:end:step], seq[start:end:step]) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought about something like this but it felt a bit too "clever" compared to the other test cases in this file, which are very straightforward. But sure, looks good! |
||||
seq = ("abc", "a", "b", "c") | ||||
indices = [None, *range(-len(seq), len(seq) + 1)] | ||||
for start, end, step in itertools.product( | ||||
indices, | ||||
indices, | ||||
filter(lambda x: x != 0, indices), # slice step cannot be zero | ||||
): | ||||
with self.subTest(start=start, end=end, step=step): | ||||
self.assertEqual(m[start:end:step], seq[start:end:step]) | ||||
|
||||
def test_match_sequence(self): | ||||
m = re.match(r"(a)(b)(c)", "abc") | ||||
self.assertIsInstance(m, Sequence) | ||||
self.assertEqual(len(m), 4) | ||||
|
||||
self.assertEqual(tuple(m), ("abc", "a", "b", "c")) | ||||
self.assertEqual(list(m), ["abc", "a", "b", "c"]) | ||||
|
||||
abc, a, b, c = m | ||||
self.assertEqual(abc, "abc") | ||||
self.assertEqual(a, "a") | ||||
self.assertEqual(b, "b") | ||||
self.assertEqual(c, "c") | ||||
|
||||
self.assertIn("abc", m) | ||||
self.assertIn("a", m) | ||||
self.assertIn("b", m) | ||||
self.assertIn("c", m) | ||||
self.assertNotIn("123", m) | ||||
|
||||
self.assertEqual(list(reversed(m)), ["c", "b", "a", "abc"]) | ||||
|
||||
for s, k, v in re.finditer(r"(\w+):(\w+)", "abc:123"): | ||||
self.assertEqual(s, "abc:123") | ||||
self.assertEqual(k, "abc") | ||||
self.assertEqual(v, "123") | ||||
|
||||
def test_match_iter(self): | ||||
it = iter(re.match(r"(a)(b)(c)", "abc")) | ||||
self.assertEqual(next(it), "abc") | ||||
self.assertEqual(next(it), "a") | ||||
self.assertEqual(next(it), "b") | ||||
self.assertEqual(next(it), "c") | ||||
self.assertRaises(StopIteration, next, it) | ||||
|
||||
def test_match_index(self): | ||||
m = re.match(r"(a)(b)(c)(b)", "abcb") | ||||
self.assertEqual(m.index("abcb"), 0) | ||||
self.assertEqual(m.index("a"), 1) | ||||
self.assertEqual(m.index("b"), 2) | ||||
picnixz marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
self.assertEqual(m.index("c"), 3) | ||||
self.assertRaises(ValueError, m.index, "123") | ||||
|
||||
# With start index. | ||||
self.assertEqual(m.index("a", 1), 1) | ||||
self.assertEqual(m.index("b", 1), 2) | ||||
self.assertEqual(m.index("c", 1), 3) | ||||
self.assertRaises(ValueError, m.index, "abcb", 1) | ||||
self.assertRaises(ValueError, m.index, "123", 1) | ||||
|
||||
self.assertEqual(m.index("b", 2), 2) | ||||
self.assertEqual(m.index("c", 2), 3) | ||||
self.assertRaises(ValueError, m.index, "abcb", 2) | ||||
self.assertRaises(ValueError, m.index, "a", 2) | ||||
self.assertRaises(ValueError, m.index, "123", 2) | ||||
|
||||
self.assertEqual(m.index("b", 3), 4) | ||||
self.assertEqual(m.index("c", 3), 3) | ||||
self.assertRaises(ValueError, m.index, "abcb", 3) | ||||
self.assertRaises(ValueError, m.index, "a", 3) | ||||
self.assertRaises(ValueError, m.index, "123", 3) | ||||
|
||||
self.assertEqual(m.index("b", 4), 4) | ||||
self.assertRaises(ValueError, m.index, "abcb", 4) | ||||
self.assertRaises(ValueError, m.index, "a", 4) | ||||
self.assertRaises(ValueError, m.index, "c", 4) | ||||
self.assertRaises(ValueError, m.index, "123", 4) | ||||
|
||||
self.assertRaises(ValueError, m.index, "abcb", 5) | ||||
self.assertRaises(ValueError, m.index, "a", 5) | ||||
self.assertRaises(ValueError, m.index, "b", 5) | ||||
self.assertRaises(ValueError, m.index, "c", 5) | ||||
self.assertRaises(ValueError, m.index, "123", 5) | ||||
|
||||
# With start index and stop index. | ||||
self.assertEqual(m.index("b", 1, 3), 2) | ||||
self.assertEqual(m.index("b", 2, 4), 2) | ||||
self.assertEqual(m.index("b", 3, 5), 4) | ||||
self.assertRaises(ValueError, m.index, "b", 0, 2) | ||||
self.assertRaises(ValueError, m.index, "b", 3, 4) | ||||
self.assertRaises(ValueError, m.index, "b", -1, 0) | ||||
|
||||
# Non-string objects. | ||||
self.assertRaises(ValueError, m.index, 123) | ||||
self.assertRaises(ValueError, m.index, [1, 2, 3]) | ||||
self.assertRaises(ValueError, m.index, object()) | ||||
|
||||
def test_match_count(self): | ||||
m = re.match(r"(a)(b)(c)", "abc") | ||||
self.assertEqual(m.count("abc"), 1) | ||||
self.assertEqual(m.count("a"), 1) | ||||
self.assertEqual(m.count("b"), 1) | ||||
self.assertEqual(m.count("c"), 1) | ||||
self.assertEqual(m.count("123"), 0) | ||||
picnixz marked this conversation as resolved.
Show resolved
Hide resolved
|
||||
|
||||
# Non-string objects. | ||||
self.assertEqual(m.count(123), 0) | ||||
self.assertEqual(m.count([1, 2, 3]), 0) | ||||
self.assertEqual(m.count(object()), 0) | ||||
|
||||
def test_match_match_case(self): | ||||
m = re.match(r"(a)(b)(c)", "abc") | ||||
|
||||
match m: | ||||
case [abc, "a", "b", "c"]: | ||||
self.assertEqual(abc, "abc") | ||||
case _: | ||||
self.fail() | ||||
|
||||
match re.match(r"(\d+)-(\d+)-(\d+)", "2025-05-07"): | ||||
case [date, year, month, day]: | ||||
self.assertEqual(date, "2025-05-07") | ||||
self.assertEqual(year, "2025") | ||||
self.assertEqual(month, "05") | ||||
self.assertEqual(day, "07") | ||||
case _: | ||||
self.fail() | ||||
|
||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||
def test_re_fullmatch(self): | ||||
# Issue 16203: Proposal: add re.fullmatch() method. | ||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
Make :class:`re.Match` a well-rounded :class:`~collections.abc.Sequence` | ||
type. |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not true with this PR, Sequence has a number of other requirements (e.g. an
index
andcount
method).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch, thanks! I added
index
andcount
by adapting the implementation oftuple.index
andtuple.count
. I also updated the unit test to cover allSequence
mixin methods.