【问题标题】:Why does (?:...) regex behave differently for re.match and re.findall?为什么 (?:...) 正则表达式对于 re.match 和 re.findall 的行为不同?
【发布时间】:2021-11-21 23:14:26
【问题描述】:

我正在研究一个 CS50 问题,您必须找到连续重复的字符串模式(DNA 序列中的核苷酸)。我想我会使用re,因为它是 Python3 分配的,我之前已经对其进行了一些修改,并认为我可以解决它......但是不。

所以我搜索了如何实现这一点,并找到了一个用户建议使用 re.findall("(?:<pattern>)+", <string>) 的主题,而这正是我解决问题所需要的。

所以我对 ?: 表达式感到好奇并查阅了文档,但无法理解 non-capturing group 的含义,并且当我找到使用字符串的解释时,我不得不再次四处寻找答案由一个网址组成。

在这个特定示例中,用户使用的是re.match

"(?:https?|ftp)://(stackoverflow.com)"

output:
    group1: (stackoverflow.com)
vs

"(https?|ftp)://(stackoverflow.com)"

output:
    group1: (https)
    group2: (stackoverflow.com)

此时我明白了非捕获意味着什么......但现在我不明白为什么?:re.matchre.findall 的行为不同,其中匹配对象,如官方文档中所述,确实如此re.findall 方法返回的列表似乎捕获了匹配的子字符串将连续重复分组为一个,而不捕获指示的组。

我最好的猜测是,由于它是非捕获的,因此该方法会继续连续查找匹配的子字符串,一旦结束,它就会“关闭”组,这就是为什么重复的子字符串被分组为一个的原因。但我仍然不知道为什么 re.findall 返回不应该被捕获的内容,如果有人能指出我正确的方向,我真的很感激。

【问题讨论】:

标签: python-3.x regex regex-greedy


【解决方案1】:

正则表达式中的“捕获组”通常用括号表示。在这样的正则表达式中:

<before>(<capture>)<after>

“捕获组”是(&lt;capture&gt;),这意味着这是我们真正想要的字符串部分 - 正则表达式的其余部分旨在针对该部分。我们需要这个来捕捉,比如说,“数字后跟'美元'”:

(\d+)\s*dollar

re.match() 返回一个Match 对象,它有几个“组”,您可以使用.group(n) 方法访问。

  • 组 0 始终包含整个匹配字符串,无论其中包含任何“匹配组”。在上面的示例中,与字符串 "35 dollar item" 匹配,它将是“35 美元”。
  • n &gt; 0 组包含 正则表达式中的第 n 个捕获组。在上面的例子中,匹配字符串"35 dollar item",它就是"35"

re.findall() 的行为因正则表达式中捕获组的数量而异:

  • 如果没有捕获组,则返回与整个正则表达式匹配的所有字符串的列表(例如.group(0)
  • 如果只有一个捕获组,则返回与该捕获组匹配的所有字符串的列表(例如.group(1)
  • 如果有多个捕获组,则返回(.group(1), .group(2), ..., .group(n)) 的元组列表。

非捕获组的想法是有时我们希望使用需要括号来消除歧义的表达式。最常见的是|。例如,如果我想获取“先生”称呼的某人的姓氏(而不是头衔)。或“女士”,我可能会像这样写一个正则表达式:

(?:Mr|Ms)\.\s*(\w+)

(?:Mr|Ms) 是一个非捕获组,因此它不会被视为自己的组(例如.group(1))。同时,(\w+) 是一个捕获组,因此 将被视为自己的组。从那里,我们可以观察re.match()re.findall() 的行为,并查看它们是否与其文档一致:

>>> regex = r'(?:Mr|Ms)\.\s*(\w+)'
>>> mo = re.match(regex, 'Mr. Big and Ms. Small')
>>> mo.group(0)
'Mr. Big'
>>> mo.group(1)
'Big'
>>> re.findall(regex, 'Mr. Big and Ms. Small')
['Big', 'Small']

因为只有一个捕获组,re.findall() 忽略组 0。

以下对比行为,将MrMs 注册为捕获组,从而产生不同的输出(由于现在有多个捕获组,因此re.findall() 更改其输出格式:

>>> regex2 = r'(Mr|Ms)\.\s*(\w+)'  # omitting the ?:
>>> re.findall(regex2, 'Mr. Big and Ms. Small')
[('Mr', 'Big'), ('Ms', 'Small')]

【讨论】:

  • 感谢您抽出宝贵时间撰写此答案!不过,我仍然不确定为什么将?:findall 一起使用会捕获模式的所有连续重复,如果您知道原因,我将不胜感激。再次感谢。
  • 具体来说,(?:) 表示您想要使用括号的语法优势,而无需实际创建一个会破坏其余正则表达式的捕获组。请参阅编辑以对比没有?: 的效果。至于为什么findall 会捕获所有出现的模式——这就是它存在的意义所在。它是“查找 all”,而不是“查找第一个”或“在字符串开头查找”(这是 .match() 所做的`
  • 我想我现在明白了,非常感谢。因此,由于:? 没有捕获,所有适合正则表达式的模式都被“堆积”成一个,直到连续性被打破,这就是我理解它的方式。
猜你喜欢
  • 2017-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-02
  • 2023-03-10
  • 1970-01-01
  • 2020-10-03
相关资源
最近更新 更多