Python 正则表达式:如何匹配特定字符串之前的任何内容并避免失败时回溯

1 投票
3 回答
688 浏览
提问于 2025-04-18 00:19

我正在尝试写一个正则表达式,目的是匹配某个特定模式之前的所有内容。这个正则表达式会继续查找其他模式,直到字符串的末尾,但有时候这个特定的模式可能不存在,这样匹配就会失败。目前我卡在这里:

.*?PATTERN

问题是,当这个特定的模式不在字符串中时,匹配会因为回溯而花费太多时间。为了缩短这个时间,我尝试使用正向前瞻来模拟原子分组,具体可以参考这个讨论(顺便说一下,我是在使用Python 2.7的re模块): Python的正则表达式有没有类似Ruby的原子分组?

于是我写了:

(?=(?P<aux1>.*?))(?P=aux1)PATTERN

当然,当这个特定的模式不在时,这个版本比之前的版本快,但问题是,它不再匹配这个特定的模式,因为“.”会匹配到字符串的末尾,而之前的状态在前瞻之后就被丢弃了。

所以我的问题是,有没有办法像.*?STRING那样匹配,同时在没有匹配时能更快地失败?

3 个回答

0

Python的文档里简单介绍了re.search()re.match()这两个函数的区别,具体的内容可以参考这个链接:http://docs.python.org/2/library/re.html#search-vs-match。特别是下面这段话很重要:

有时候你可能会想继续使用re.match(),然后在你的正则表达式前面加上.*。但要抵制这个诱惑,应该使用re.search()。正则表达式编译器会对正则表达式进行一些分析,以加快查找匹配的速度。其中一个分析会确定匹配的第一个字符是什么;比如,一个以Crow开头的模式必须以'C'开头。这个分析让引擎可以快速扫描字符串,寻找起始字符,只有在找到'C'时才会尝试完整匹配。

如果你加上.*,就会破坏这个优化,导致需要扫描整个字符串,然后再回溯去找剩下的匹配。还是用re.search()吧。

在你的情况下,最好把你的模式简单定义为:

pattern = re.compile("PATTERN")

然后调用pattern.search(...),这样在找不到模式时就不会回溯了。

1

你可以试试用 split 这个方法。

如果结果的长度是1,说明没有找到匹配的内容。如果结果有两个或更多,那第一个就是你找到的第一个匹配项。如果你把分割的数量限制为1,这样后面的匹配就不会继续查找了:

"HI THERE THEO".split("TH", 1) # ['HI ', 'ERE THEO']

结果的第一个元素就是匹配到的内容。

0

一种正则表达式解决方案

^(?=(?P<aux1>(?:[^P]|P(?!ATTERN))*))(?P=aux1)PATTERN

解释

你想用原子分组来写正则表达式,比如这样:(?>.*?)PATTERN,对吧?但这样是行不通的。问题在于,你不能在原子分组的末尾使用懒惰量词:原子分组的定义是,一旦你离开了这个分组,正则引擎就不会再回头去检查里面的内容。

所以正则引擎会匹配.*?,因为它是懒惰的,会先走出分组去检查下一个字符是否是P,如果不是,它就无法回到分组里去匹配.*里面的下一个字符。

在Perl中,通常会使用这样的结构:(?>(?:[^P]|P(?!ATTERN))*)PATTERN。这样,等同于.*的部分(这里是(?:[^P]|P(?!ATTERN)))就不会“吞掉”你想要的模式。

我觉得用占有量词写这个模式会更容易理解,这种量词就是为这种情况设计的:(?:[^P]|P(?!ATTERN))*+PATTERN

用你的方法转换后,这将导致上面的正则表达式(加上^是因为你应该将正则表达式锚定在字符串的开头或其他正则表达式上)。

撰写回答