Python正则表达式是否有类似于Ruby原子分组的等效功能?

38

Ruby的正则表达式有一种称为原子组合的功能(?>regexp),在这里描述,Python的re模块中是否有相应的功能?


(?>...) 在3.11中新增 - dugres
5个回答

53
Python不直接支持此功能,但您可以通过使用零宽度前瞻断言((?=RE))来模拟它,该前瞻从当前点开始匹配具有您所需的相同语义,将命名组((?P<name>RE))放在前瞻内部,然后使用命名反向引用((?P=name))精确匹配零宽度断言匹配的内容。将它们结合起来,就可以以相同的语义实现,但需要创建一个额外的匹配组和大量的语法。
例如,您提供的链接给出了Ruby示例。
/"(?>.*)"/.match('"Quote"') #=> nil

我们可以在Python中模拟这个过程,如下所示:
re.search(r'"(?=(?P<tmp>.*))(?P=tmp)"', '"Quote"') # => None

我们可以证明我正在做一些有用的事情,而不仅仅是喷出一堆无意义的代码,因为如果我们将内部组不吃掉最后一个"的内容进行更改,它仍然匹配:
re.search(r'"(?=(?P<tmp>[A-Za-z]*))(?P=tmp)"', '"Quote"').groupdict()
# => {'tmp': 'Quote'}

您也可以使用匿名组和数字反向引用,但这会变得非常混乱:

re.search(r'"(?=(.*))\1"', '"Quote"') # => None

(完整披露:我从Perl的perlre文档中学到了这个技巧,在(?>...)的文档下提到了它。)
除了具有正确的语义外,这也具有适当的性能特性。如果我们将一个例子从perlre移植出来:
[nelhage@anarchique:~/tmp]$ cat re.py
import re
import timeit


re_1 = re.compile(r'''\(
                           (
                             [^()]+           # x+
                           |
                             \( [^()]* \)
                           )+
                       \)
                   ''', re.X)
re_2 = re.compile(r'''\(
                           (
                             (?=(?P<tmp>[^()]+ ))(?P=tmp) # Emulate (?> x+)
                           |
                             \( [^()]* \)
                           )+
                       \)''', re.X)

print timeit.timeit("re_1.search('((()' + 'a' * 25)",
                    setup  = "from __main__ import re_1",
                    number = 10)

print timeit.timeit("re_2.search('((()' + 'a' * 25)",
                    setup  = "from __main__ import re_2",
                    number = 10)

我们看到了显著的改善:
[nelhage@anarchique:~/tmp]$ python re.py
96.0800571442
7.41481781006e-05

随着搜索字符串长度的增加,情况变得更加戏剧化。

1
这在Python的“re”库中仍然是正确的,但值得注意的是,“regex”库提供了原子分组和占有量化。虽然“伪原子分组”在某些情况下提供了最小的加速,但与使用正则表达式(包括“普通”和带有占有量词的)相比,它显得微不足道。 - grjash
我还发现了一个情况,使用“伪原子分组”似乎是合适的,但实际上运行速度明显较慢: 在一个以冒号结尾的非常长的单词字符字符串上匹配 ^\w+: - grjash

16
根据这个表格,答案是否定的。曾有一项RFE建议将其添加到Python 3中,但被拒绝了,而是支持它的新的regex模块。
>>> import regex
>>> regex.match('"(?>.*)"', '"Quote"')
>>> regex.match('"(.*)"', '"Quote"')
<_regex.Match object at 0x00C6F058>

注:Python 2也可使用regex


1
你提供的 regex 模块与你提供的 Python bug 中的 regex 模块是同一个模块,也就是说,在 Python 3.3 的标准库中没有支持该模块。 - jfs
@J.F.Sebastian 哦,好的!我以为Python 3仍然使用re模块,因为我只用过Python 2,所以不确定,并且假设它指的是同一个模块。 (编辑: 不用管那个了,刚看到错了:我看的是这个RFE,它被关闭为我在答案中提到的那个的重复项。我会更新它) - mgibsonbr
现在,你已经更改了链接,我的先前评论就没有意义了。记录一下,链接是http://pypi.python.org/pypi/regex和http://bugs.python.org/issue2636。 - jfs

7
截至2022年3月21日,大约在此讨论串创建后的10年,Python 3.11.0a7版本的标准库 re 模块终于加入了原子组和占有匹配功能(并且拥有控制权):提交链接。因此,现在,本讨论串中所有的答案可能都已经过时;在此之前,只有第三方的regex模块具有原子组和占有匹配功能,现在这个功能已经内置到Python的re模块中了。

0

看起来不是这样。

http://www.regular-expressions.info/atomic.html

原子分组被大多数现代正则表达式引擎所支持,包括JGsoft风格、Java、PCRE、.NET、Perl和Ruby。
您可以通过使用非捕获分组 (?:RE) 来模拟它们的非捕获性,但如果我理解正确的话,这仍然无法为您带来优化的好处。

-2

来自http://docs.python.org/2/library/re.html#regular-expression-syntax

(?P<name>...)

类似于普通括号,但是通过符号组名name可以在正则表达式的其余部分中访问与组匹配的子字符串。组名必须是有效的Python标识符,并且每个组名必须在正则表达式中仅定义一次。符号组也是一个编号组,就像该组没有命名一样。因此,下面示例中命名为id的组也可以作为编号组1进行引用。

例如,如果模式是(?P[a-zA-Z_]\w*),则组可以通过其名称在match对象的方法参数中引用,例如m.group('id')或m.end('id'),并且还可以通过名称在正则表达式本身(使用(?P=id))和提供给.sub()的替换文本(使用\g)中进行引用。

(?P=name)

Matches whatever text was matched by the earlier group named name.

4
命名捕获不同于原子分组。 - Alex Gaynor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接