为什么最小(非贪婪)匹配会受到字符串结尾字符'$'的影响?
编辑:删除了原来的例子,因为引发了其他的回答。标题也进行了修正。
问题是,为什么正则表达式中的“$”会影响表达式的贪婪程度:
这里有一个更简单的例子:
>>> 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 个回答
在正则表达式中出现的 $
符号并不会影响表达式的贪婪程度。它只是增加了一个额外的条件,只有满足这个条件,整体匹配才会成功。
无论是 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(文本导向)正则表达式相同的结果。
非贪婪修饰符只影响匹配的结束位置,而不会影响它的开始位置。如果你想让匹配尽可能晚开始,你需要在模式的开头加上.+?
。
没有$
的情况下,你的模式可以不那么贪婪,提前停止,因为它不需要匹配到字符串的末尾。
编辑:
更多细节……在这种情况下:
re.search(r"a+?$", "baaaaaaaa")
正则表达式引擎会忽略直到第一个'a'之前的所有内容,因为这就是re.search
的工作方式。它会匹配第一个a
,并且“想要”返回一个匹配结果,但因为它还没有匹配到$
,所以不能返回结果。因此,它会一个一个地继续匹配a
,并检查是否有$
。如果是贪婪模式,它就不会在每个a
后面检查$
,而是等到再也匹配不到a
时才检查。
但在这种情况下:
re.search(r"a+?", "baaaaaaaa")
正则表达式引擎会在匹配到第一个结果后检查是否有完整的匹配(因为它是非贪婪的),并且会成功,因为在这种情况下没有$
。
匹配是按照“从左到右,优先最长”的规则来进行的。不过,“最长”这个说法是在允许非贪婪匹配之前的说法,现在它的意思更像是“每个元素的优先重复次数”。在这个过程中,最左边的匹配是更重要的,而不是重复的次数。因此,“a+?$”在“baaaaa”这个字符串中不会匹配到最后一个A,因为在字符串中第一个A的匹配开始得更早。
(这个回答在提问者在评论中澄清后进行了修改。可以查看历史记录了解之前的内容。)