无法使非贪婪匹配工作

Can't make non-greedy match work

在 Python3.4 中,我使用了 re 库(regex 库给出了相同的结果),但我得到了我不期望的结果。

我有一个字符串 s = 'abc'。我期望以下正则表达式:

re.match(r"^(.*?)(b?)(.*?)$", s).groups()

..匹配三个非空组,即:

('a', 'b', 'c')

--因为模式的中间部分是贪心的(b?)。相反,只有最后一组是非空的:

('', '', 'abc')

我得到了以下两个相同的结果:

re.match(r"^(.*?)(b?)(.*?)$", s).groups()   #overt ^ and #
re.fullmatch("(.*?)(b?)(.*?)", s).groups()  #fullmatch()

如果我让第一组成为贪心匹配,那么结果是:

('abc', '', '')

我想这是我所期望的,因为贪婪的 .* 在其他组看到它之前就消耗了整个字符串。

我要构建的正则表达式当然比这更复杂,否则,我可以将 b 从左右组中排除:

re.match(r"^([^b]*?)(b?)([^b]*?)$", s).groups()

但在我的实际用例中,中间组是几个字符长的字符串,其中任何一个都可能单独出现在左侧或右侧组中,所以我不能只从左侧排除那些字符或右组。

我查看了其他标记为 , and none seems to answer this question, although I suspect that ctwheels' reply in 的问题背后是我的问题(前两组的可选性防止正则表达式引擎在到达字符串末尾之前实际失败,然后它只需要回溯一些方法来获得不失败的匹配。

I would expect the following regex

re.match(r"^(.*?)(b?)(.*?)$", s).groups()

to match with three non-empty groups.. because the middle part of the pattern is greedy

不,你不应该期望那样。实际上,由于以下原因,这种行为是非常意料之中的:

您明确指示第一组中的正则表达式 lazy,这意味着它将接受尽可能少的字符(在本例中为零) 因为没有别的东西迫使它寻找更多。因此,尽管第二组中的正则表达式是贪心的(即 b?),但它仍然无法匹配 b,因为位置仍为 0.

您可以通过将第二组替换为 (.?) 来确认 在这种情况下 将匹配 a 而不是b 符合您的预期^(.*?)(.?)(.*?)$.

a demo

现在,如果您的规则不允许缺少 b,您可以轻松地将正则表达式更改为 ^(.*?)(b)(.*?)$,但由于您希望第一组继续匹配 如果b存在但同时,则允许b不存在(即第二组可以实际为空),则此方案不能解决问题

目前我想到的唯一满足这两个条件的方案就是用Lookahead判断b是否存在。这是一个例子:

^((?:.*?(?=b))|.*?)(b?)(.*?)$

Try it online.

这将继续匹配任何字符(使用 .)直到它找到 b 然后停止,否则(即,如果没有 b),它将只要找到尽可能少的字符(这是原始行为),就停止匹配。也就是说,只要b存在.

,就会保证第二组不为空

如果这不符合您的任何条件,请告诉我。

由于目标是根据中间的模式将字符串分成三部分,您可以搜索该模式并使用其开始和结束索引自行拆分字符串。

import re

def combo_finder(line):
    try:
        search = re.search("(foo|bar|baz)", line)
        start, end = search.start(1), search.end(1)
        return (line[:start], line[start:end], line[end:])
    except AttributeError:
        return (line, '', '')

test = ("afoob", "abarb", "afoo", "ab")

for s in test:
    print(s, combo_finder(s))

这个测试运行给出

afoob ('a', 'foo', 'b')
abarb ('a', 'bar', 'b')
afoo ('a', 'foo', '')
ab ('ab', '', '')

自己回答(尽管正如我在评论中所说,我选择 Ahmed 的回答作为答案)。可能这会帮助其他人。我的解决方案类似于 tdelaney 的解决方案,但使用 if/else 而不是 try/except,并得到不同的答案。这是代码:

rxRX = re.compile("^(.*)(foo|bar|baz)(.*)$")
Match = rxRX.match(sLine)
if Match:
     return [G for G in Match.groups()]
else: #rxRX didn't match, so just return the input:
     return [sLine]

你的答案很好,但我会更具体地说明这个要求:

But in my real use case, the middle group is a string several characters long, any of which might show up on their own in the left or right groups, so I can't just exclude those chars from the left or right groups.

无论中间组是什么,您都可以在查找时使用模式来允许/禁止匹配:

^((?:(?!GROUP2).)*)(GROUP2)((?:!GROUP2).)*)$

所以如果 GROUP2b 它是:

^((?:(?!b).)*)(b)((?:(?!b).)*)$

在正则表达式世界中,它被称为 tempered dot

Live demo