mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 18:59:04 +08:00
findall returns the matched text rather than a match object, so a group that
took no part in the match is reported as an empty value. With one group it
was reported as `None`:
>>> re.findall(r"(a)?b", "b ab")
[None, 'a'] # CPython: ['', 'a']
The branch for two or more groups was already right, since it passes `""` to
`Match.groups` as the default, so the two halves of the same function
disagreed with each other.
That default is built as a `str` whatever the pattern is, so a `bytes`
pattern came back with `str` mixed into it:
>>> re.findall(rb"(a)|(b)", b"ab")
[(b'a', ''), ('', b'b')] # CPython: [(b'a', b''), (b'', b'b')]
The empty value is now built once from `isbytes` and both branches use it.
`Match.groups` still reports `None`, which is what CPython does.
Assisted-by: Claude Code:claude-opus-5
129 lines
4.6 KiB
Python
129 lines
4.6 KiB
Python
import re
|
|
|
|
haystack = "Hello world"
|
|
needle = "ello"
|
|
|
|
mo = re.search(needle, haystack)
|
|
print(mo)
|
|
|
|
# Does not work on python 3.6:
|
|
assert isinstance(mo, re.Match)
|
|
assert mo.start() == 1
|
|
assert mo.end() == 5
|
|
|
|
assert re.escape("python.exe") == "python\\.exe"
|
|
|
|
p = re.compile("ab")
|
|
s = p.sub("x", "abcabca")
|
|
# print(s)
|
|
assert s == "xcxca"
|
|
|
|
idpattern = r"([_a-z][_a-z0-9]*)"
|
|
|
|
mo = re.search(idpattern, "7382 _boe0+2")
|
|
assert mo.group(0) == "_boe0"
|
|
|
|
# tes op range
|
|
assert re.compile("[a-z]").match("a").span() == (0, 1)
|
|
assert re.compile("[a-z]").fullmatch("z").span() == (0, 1)
|
|
|
|
# test op charset
|
|
assert re.compile("[_a-z0-9]*").match("_09az").group() == "_09az"
|
|
|
|
# test op bigcharset
|
|
assert re.compile("[你好a-z]*").match("a好z你?").group() == "a好z你"
|
|
assert re.compile("[你好a-z]+").search("1232321 a好z你 !!?").group() == "a好z你"
|
|
|
|
# test op repeat one
|
|
assert re.compile("a*").match("aaa").span() == (0, 3)
|
|
assert re.compile("abcd*").match("abcdddd").group() == "abcdddd"
|
|
assert re.compile("abcd*").match("abc").group() == "abc"
|
|
assert re.compile("abcd*e").match("abce").group() == "abce"
|
|
assert re.compile("abcd*e+").match("abcddeee").group() == "abcddeee"
|
|
assert re.compile("abcd+").match("abcddd").group() == "abcddd"
|
|
|
|
# test op mark
|
|
assert re.compile("(a)b").match("ab").group(0, 1) == ("ab", "a")
|
|
assert re.compile("a(b)(cd)").match("abcd").group(0, 1, 2) == ("abcd", "b", "cd")
|
|
|
|
# test op repeat
|
|
assert re.compile("(ab)+").match("abab")
|
|
assert re.compile("(a)(b)(cd)*").match("abcdcdcd").group(0, 1, 2, 3) == (
|
|
"abcdcdcd",
|
|
"a",
|
|
"b",
|
|
"cd",
|
|
)
|
|
assert re.compile("ab()+cd").match("abcd").group() == "abcd"
|
|
assert re.compile("(a)+").match("aaa").groups() == ("a",)
|
|
assert re.compile("(a+)").match("aaa").groups() == ("aaa",)
|
|
|
|
# test Match object method
|
|
assert re.compile("(a)(bc)").match("abc")[1] == "a"
|
|
assert re.compile("a(b)(?P<a>c)d").match("abcd").groupdict() == {"a": "c"}
|
|
|
|
# test op branch
|
|
assert re.compile(r"((?=\d|\.\d)(?P<int>\d*)|a)").match("123.2132").group() == "123"
|
|
|
|
assert re.sub(r"^\s*", "X", "test") == "Xtest"
|
|
|
|
assert re.match(r"\babc\b", "abc").group() == "abc"
|
|
|
|
urlpattern = re.compile("//([^/#?]*)(.*)", re.DOTALL)
|
|
url = "//www.example.org:80/foo/bar/baz.html"
|
|
assert urlpattern.match(url).group(1) == "www.example.org:80"
|
|
|
|
assert re.compile("(?:\w+(?:\s|/(?!>))*)*").match("a /bb />ccc").group() == "a /bb "
|
|
assert re.compile("(?:(1)?)*").match("111").group() == "111"
|
|
|
|
# Test of fix re.fullmatch POSSESSIVE_REPEAT, issue #7183
|
|
assert re.fullmatch(r"([0-9]++(?:\.[0-9]+)*+)", "1.25.38")
|
|
assert re.fullmatch(r"([0-9]++(?:\.[0-9]+)*+)", "1.25.38").group(0) == "1.25.38"
|
|
|
|
# Combining characters; issue #7518
|
|
assert not re.match(r"\w", "\u0345"), r"\w should not match U+0345 (category Mn)"
|
|
|
|
|
|
def test_findall_group_that_did_not_participate():
|
|
# findall returns the matched text, not a match object, so a group that
|
|
# took no part in the match stands in as an empty value of the type the
|
|
# pattern works on. One group used to come back as None, and a bytes
|
|
# pattern used to mix str into its results.
|
|
assert re.findall(r"(a)?b", "b ab") == ["", "a"]
|
|
assert re.findall(r"(x)?", "a") == ["", ""]
|
|
assert re.findall(r"(a|b)?c", "c ac bc") == ["", "a", "b"]
|
|
assert re.findall(r"(?P<g>a)?b", "b ab") == ["", "a"]
|
|
assert re.compile(r"(a)?b").findall("b ab") == ["", "a"]
|
|
|
|
assert re.findall(rb"(a)?b", b"b ab") == [b"", b"a"]
|
|
assert re.findall(rb"(x)?", b"a") == [b"", b""]
|
|
|
|
# Two or more groups give a tuple per match, with the same stand-in.
|
|
assert re.findall(r"(a)|(b)", "ab") == [("a", ""), ("", "b")]
|
|
assert re.findall(rb"(a)|(b)", b"ab") == [(b"a", b""), (b"", b"b")]
|
|
assert re.findall(rb"(a)(b)?", b"a ab") == [(b"a", b""), (b"a", b"b")]
|
|
|
|
# The type is the pattern's, never the other one.
|
|
assert [type(x) for x in re.findall(r"(a)?b", "b ab")] == [str, str]
|
|
assert [type(x) for x in re.findall(rb"(a)?b", b"b ab")] == [bytes, bytes]
|
|
assert [type(y) for x in re.findall(rb"(a)|(b)", b"ab") for y in x] == [
|
|
bytes,
|
|
bytes,
|
|
bytes,
|
|
bytes,
|
|
]
|
|
|
|
# A group that does participate, and no group at all, are unchanged.
|
|
assert re.findall(r"(a)", "aa") == ["a", "a"]
|
|
assert re.findall(r"a", "aa") == ["a", "a"]
|
|
assert re.findall(rb"a", b"aa") == [b"a", b"a"]
|
|
|
|
# A match object still reports None, which is where the difference lies.
|
|
assert re.match(r"(a)?b", "b").groups() == (None,)
|
|
assert re.match(r"(a)?b", "b").group(1) is None
|
|
assert [m.groups() for m in re.finditer(r"(a)?b", "b ab")] == [(None,), ("a",)]
|
|
assert re.split(r"(a)|(b)", "xaybz") == ["x", "a", None, "y", None, "b", "z"]
|
|
|
|
|
|
test_findall_group_that_did_not_participate()
|