正则表达式——滥用点号的问题
因为点号能匹配几乎所有的字符,所以实际应用中许多人图省事,随意使用.*或.+,结果却事与愿违,下面以双引号字符串为例来说明。
之前我们使用表达式"[^"]*"匹配双引号字符串,而"图省事"的做法是".*"。通常这么用是没有问题的,但也可能有意外,例2-12就说明了一种如此。
例2-12 "图省事"的意外结果
#字符串的值是"quoted string" print re.search(r"\".*\"", "\"quoted string\"").group(0) "quoted string" #字符串的值是"quoted string" and another" print re.search(r"\".*\"", "\"quoted string\" and another\"").group(0) "quoted string" and another" `
用".*"匹配双引号字符串,不但可以匹配正常的双引号字符串"quoted string",还可以匹配格式错误的字符串"quoted string" and another"。这是为什么呢?
这个问题比较复杂,现在只简要介绍,以说明图省事导致错误的原因,更深入的原因涉及正则表达式的匹配原理,在第8章详细介绍。
在正则表达式".*"中,点号.可以匹配任何字符,*表示可以匹配的字符串长度没有限制,所以.*在匹配过程结束以前,每遇到一个字符(除去无法匹配的\n),.*都可以匹配,但是到底是匹配这个字符,还是忽略它,将其交给之后的"来匹配呢?
答案是,具体选择取决于所使用的量词。在正则表达式中的量词分为几类,之前介绍的量词都可以归到一类,叫做匹配优先量词(greedy quantifier,也有人翻译为贪婪量词 )。匹配优先量词,顾名思义,就是在拿不准是否要匹配的时候,优先尝试匹配,并且记下这个状态,以备将来"反悔"。
来看表达式".*"对字符串"quoted string"的匹配过程。
一开始,"匹配",然后轮到字符q,.*可以匹配它,也可以不匹配,因为使用了匹配优先量词,所以.*先匹配q,并且记录下这个状态【q也可能是.*不应该匹配的】;
接下来是字符u,.*可以匹配它,也可以不匹配,因为使用了匹配优先量词,所以.*先匹配u,并且记录下这个状态【u也可能是.*不应该匹配的】;
……
现在轮到字符g,.*可以匹配它,也可以不匹配,因为使用了匹配优先量词,所以.*先匹配g,并且记录下这个状态【g也可能是.*不应该匹配的】;
最后是末尾的",.*可以匹配它,也可以不匹配,因为使用了匹配优先量词,所以.*先匹配",并且记录下这个状态【"也可能是.*不应该匹配的】。
这时候,字符串之后已经没有字符了,但正则表达式中还有"没有匹配,所以只能查询之前保存备用的状态,看看能不能退回几步,照顾"的匹配。查询到最近保存的状态是:【"也可能是.*不应该匹配的】。于是让.*"反悔"对"的匹配,把"交给",测试发现正好能匹配,所以整个匹配宣告成功。这个"反悔"的过程,专业术语叫做回溯(backtracking),具体的过程如图2-1所示。
图2-1 表达式".*"对字符串"quoted string"的匹配过程 |
如果把字符串换成"quoted string" and another",.*会首先匹配第一个双引号之后的所有字符,再进行回溯,表达式中的"匹配了字符串结尾的字符",整个匹配宣告完成,过程如图2-2所示。
图2-2 表达式".*"的匹配过程 |
如果要准确匹配双引号字符串,就不能图省事使用".*",而要使用"[^"]*",过程如图2-3所示。
图2-3 表达式"[^"]*"的匹配过程 |