Python的正则表达式在反向引用模式中找不到最后一个空字符串

编程语言 2026-07-09

以下代码包含两个模式,第一个模式是 (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" 进行搜索,情况会更有趣:

  1. re.findall(r'(1)?\1', "111") -> ['1']
  2. re.findall(r'(1?)\1', "111") -> ['1', '', '']
  3. re.findall(r'(1??)\1', "111") -> ['', '1', '', '']
  4. re.findall(r'(1)?\1', "1111") -> ['1', '1']
  5. re.findall(r'(1?)\1', "1111") -> ['1', '1', '']
  6. re.findall(r'(1??)\1', "1111") -> ['', '1', '', '1', '']

示例5 和6 更能清楚地体现贪婪与非贪婪运算符之间的差异:标准的贪婪运算符无法匹配字符串中第一个 11 与第二个 11 之间的那个间隙,而非贪婪运算符则可以。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章