Python的正则表达式在反向引用模式中找不到最后一个空字符串
以下代码包含两个模式,第一个模式是 (1)?,第二个模式是 (1)?\1。
findall_no_reference 的输出是正确的,因为它包含了最后一个空字符串,但 findall_with_reference 的输出是错误的,因为它没有包含最后一个空字符串。
据我的理解,(1)?\1 中的最后一个空字符串 '' 也应该存在于 11,于是:
"(1)?\1" = "last empty string" + "back reference to last empty string" = ''
但当模式为 (1)?\1 时,Python只找到一个匹配。
下面是测试代码(在bash终端中测试过):
import re
pattern = "(1)?"
string = "11"
print("findall_no_reference: ", [match for match in re.findall(pattern, string)])
pattern = "(1)?\\1"
string = "11"
print("findall_with_reference: ", [match for match in re.findall(pattern, string)])
结果如下:
findall_no_reference: ['1', '1', '']
findall_with_reference: ['1']
预期输出如下:
findall_no_reference: ['1', '1', '']
findall_with_reference: ['1', '']
为什么最后一个空字符串 '' 没有包含在 findall_with_reference 中?
解决方案
在正则表达式中,允许匹配0 个字符的模式可以匹配被搜索字符串的第一个字符之前、字符之间以及最后一个字符之后的“间隙”,因此模式 (1)? 将在捕获组1 中匹配一个 1,也可以匹配为空(并且不会有捕获组1)。
此外,除非另有说明,正则表达式通常是“贪婪的”,这意味着模式的每一部分都会尽量匹配尽可能长的字符串。
用 findall() 对字符串 "11" 进行匹配时,这意味着 (1)? 模式将首先在捕获组1 中匹配第一个 1,然后在捕获组1 中匹配第二个 1,再者,字符串的末尾将匹配因跳过可选捕获组而产生的空模式。
在模式 (1)?\1 中,\1 反向引用是必需的:这意味着为了让模式匹配,捕获组1 必须存在。使用字符串 "11",模式中的 (1)? 部分将匹配捕获组1 中的第一个 1,而 \1 反向引用随后匹配第二个 1,从而使整个模式匹配。当它到达字符串末尾时,模式的前半部分将匹配空字符串,但没有捕获组,因此反向引用无法匹配,整个模式失败。
如果将 ? 移到捕获组内部,那么模式 (1?)\1 将按预期先匹配成对的 1,但在字符串末尾,可选的 1? 匹配将匹配空字符串,且会被捕获在组1 中,从而使反向引用也匹配字符串末尾的空隙,最终得到列表 ['1', '']。
如果随后把 ? 运算符改为非贪婪(在运算符后面添加一个 ?),那么模式 (1??)\1 会产生新效果:因为 ?? 运算符是非贪婪的,它不必匹配第一个 1,因此现在会在搜索开始处匹配空字符串,将其捕获到组1 中,反向引用也会匹配;其余的搜索按前文所述进行,但这次你会得到列表 ['', '1', '']。
如果你对字符串 "111" 和 "1111" 进行搜索,情况会更有趣:
re.findall(r'(1)?\1', "111")->['1']re.findall(r'(1?)\1', "111")->['1', '', '']re.findall(r'(1??)\1', "111")->['', '1', '', '']re.findall(r'(1)?\1', "1111")->['1', '1']re.findall(r'(1?)\1', "1111")->['1', '1', '']re.findall(r'(1??)\1', "1111")->['', '1', '', '1', '']
示例5 和6 更能清楚地体现贪婪与非贪婪运算符之间的差异:标准的贪婪运算符无法匹配字符串中第一个 11 与第二个 11 之间的那个间隙,而非贪婪运算符则可以。