为什么最小(非贪婪)匹配会受到字符串结尾字符'$'的影响?

7 投票
6 回答
4192 浏览
提问于 2025-04-16 16:56

编辑:删除了原来的例子,因为引发了其他的回答。标题也进行了修正。

问题是,为什么正则表达式中的“$”会影响表达式的贪婪程度:

这里有一个更简单的例子:

>>> import re
>>> str = "baaaaaaaa"
>>> m = re.search(r"a+$", str)
>>> m.group()
'aaaaaaaa'
>>> m = re.search(r"a+?$", str)
>>> m.group()
'aaaaaaaa'

在这个例子中,“?”似乎没有起到什么作用。不过注意,当“$”被去掉时,“?”就开始发挥作用了:

>>> m = re.search(r"a+?", str)
>>> m.group()
'a'

编辑:换句话说,“a+?$”会匹配所有的“a”,而不仅仅是最后一个,这并不是我预期的结果。这里是正则表达式“+?”在Python文档中的描述:“在限定符后面加上‘?’会让匹配变得非贪婪或最小化;会尽可能少地匹配字符。”

但在这个例子中似乎并不是这样:字符串“a”匹配正则表达式“a+?$”,那么为什么在字符串“baaaaaaa”上用同样的正则表达式匹配时,结果不是只匹配一个“a”(最右边的那个)呢?

6 个回答

4

在正则表达式中出现的 $ 符号并不会影响表达式的贪婪程度。它只是增加了一个额外的条件,只有满足这个条件,整体匹配才会成功。

无论是 a+ 还是 a+?,它们都必须先找到并处理第一个 a。如果这个 a 后面还有更多的 a,那么 a+ 会继续处理这些 a,而 a+? 只会满足于处理第一个。如果正则表达式还有其他内容,a+ 可能会愿意处理更少的 a,而 a+? 则会处理更多的 a,只要这样能成功匹配。

当你使用 a+$a+?$ 时,你又增加了一个条件:至少要匹配一个 a,并且这个 a 后面要跟着字符串的结束。a+ 仍然会一开始就处理所有的 a,然后再交给 $。这次匹配在第一次就成功了,所以 a+ 不需要把任何 a 还回去。

另一方面,a+? 一开始只处理一个 a,然后交给 $。这次匹配失败了,所以控制权又回到 a+?,它再处理一个 a,然后再次交给 $。就这样一直进行,直到 a+? 处理完最后一个 a$ 最终成功匹配。所以,a+?$ 确实匹配的 a 的数量和 a+$ 一样,但它是勉强匹配的,而不是贪婪匹配的。

至于之前提到的“最左最长”规则,这个规则并不适用于像 Python 这样的 Perl 派生的正则表达式。即使没有勉强量词,它们也总是能通过 有序选择 返回一个不超过最大匹配的结果。我认为 Jan 的想法是对的:Perl 派生(或正则表达式导向)的类型应该称为 急切,而不是贪婪。

我相信“最左最长”规则只适用于 POSIX NFA 正则表达式,这些表达式在底层使用 NFA 引擎,但需要返回与 DFA(文本导向)正则表达式相同的结果。

4

非贪婪修饰符只影响匹配的结束位置,而不会影响它的开始位置。如果你想让匹配尽可能晚开始,你需要在模式的开头加上.+?

没有$的情况下,你的模式可以不那么贪婪,提前停止,因为它不需要匹配到字符串的末尾。

编辑:

更多细节……在这种情况下:

re.search(r"a+?$", "baaaaaaaa")

正则表达式引擎会忽略直到第一个'a'之前的所有内容,因为这就是re.search的工作方式。它会匹配第一个a,并且“想要”返回一个匹配结果,但因为它还没有匹配到$,所以不能返回结果。因此,它会一个一个地继续匹配a,并检查是否有$。如果是贪婪模式,它就不会在每个a后面检查$,而是等到再也匹配不到a时才检查。

但在这种情况下:

re.search(r"a+?", "baaaaaaaa")

正则表达式引擎会在匹配到第一个结果后检查是否有完整的匹配(因为它是非贪婪的),并且会成功,因为在这种情况下没有$

4

匹配是按照“从左到右,优先最长”的规则来进行的。不过,“最长”这个说法是在允许非贪婪匹配之前的说法,现在它的意思更像是“每个元素的优先重复次数”。在这个过程中,最左边的匹配是更重要的,而不是重复的次数。因此,“a+?$”在“baaaaa”这个字符串中不会匹配到最后一个A,因为在字符串中第一个A的匹配开始得更早。

(这个回答在提问者在评论中澄清后进行了修改。可以查看历史记录了解之前的内容。)

撰写回答