标准正则表达式与Python正则表达式的差异

5

我正在阅读一本书,书中提供了一个关于如何使用正则表达式匹配给定字符串的例子。以下是他们的示例:

b*(abb*)*(a|∊) - Strings of a's and b's with no consecutive a's.

现在我已经尝试将其转换为Python,如下所示:
>> p = re.compile(r'b*(abb*)*(a|)') # OR
>> p = re.compile(r'b*(abb*)*(a|\b)')

# BUT it still doesn't work
>>> p.match('aa')
<_sre.SRE_Match object at 0x7fd9ad028c68>

我的问题有两个方面:

  1. 在Python中,如何相应地使用 epsilon 使上述示例起作用?
  2. 有人能够解释一下为什么理论或标准的正则表达式方法在Python中不起作用吗?这可能与最长匹配和最短匹配有关吗?

澄清:对于那些问什么是标准正则表达式的人 - 它是形式语言理论标准:http://en.wikipedia.org/wiki/Regular_expression#Formal_language_theory


1
在这种情况下,epsilon的预期行为是什么?我以前从未见过这样的东西,而我已经编程12年了... - jathanism
6
@S.Lott说到一个名叫Kleene的家伙;请参见http://en.wikipedia.org/wiki/Stephen_Kleene ... “他还发明了正则表达式”。 - John Machin
2
Epsilon是“标准”Kleene正则表达式中的空字符串。正则表达式起源于计算理论(数学),远在它们进入编程语言之前就已经存在了很长时间... - Brian Postow
4
尝试阅读OP所写的内容:“正则表达式的理论或标准方法”。当然这不是一个具体的实现。他询问为什么他的Python解释(其中一个是正确的(仅省略了epsilon))与他在书中看到的不匹配。这就是我们应该帮助解决的问题。答案是该表达式也可以匹配零长度字符串,并且需要添加$(或更好的\Z)才能获得所需的效果。如果有一个关于三角学书中公式以及尝试Python实现的问题,这样做没问题吗? - John Machin
3
并非所有标准都来自标准制定组织。此外,并非所有标准化的事物在软件工程意义上都是标准。是哪个标准组织决定莱布尼兹符号将成为“标准微积分符号”?任何学过计算理论课程的人都应该知道正则表达式的“常规”方式:使用 Kleene 符号。此外,Kleene 符号早于标准组织的概念。 - Brian Postow
显示剩余4条评论
7个回答

5
实际上,这个例子运行得很好……只有一些小细节问题。我会这样写:
>>> p = re.compile('b*(abb*)*a?')
>>> m = p.match('aa')
>>> print m.group(0)
'a'
>>> m = p.match('abbabbabababbabbbbbaaaaa')
>>> print m.group(0)
abbabbabababbabbbbba

请注意,组 0 返回正则表达式匹配的字符串部分。
如您所见,该表达式匹配一系列不重复 a 的连续 b。如果确实需要检查整个字符串,则需要稍作修改:
>>> p = re.compile('^b*(abb*)*a?$')
>>> m = p.match('aa')
>>> print m
None

^$强制识别字符串的开头和结尾。

最后,您可以通过使用第一个正则表达式,并在末尾进行测试来结合两种方法:

>>> len(m.group(0)) == len('aa')

新增: 对于OT的第二部分,我认为标准正则表达式和Python实现之间没有任何差异。当然,符号略有不同,而Python实现提供了一些扩展(与大多数其他软件包一样)。


打败我回答问题加一分!:) 顺便说一下,^不是必需的,因为re.match()只尝试在字符串的开头匹配模式。 - João Portela
哦,你的例子是错误的。p = re.compile('b*(abb)*a?') 不匹配 'aba'。 - João Portela
糟糕..在第一个正则表达式中忘记了一个星号...已经更正了! - PierreBdR
你还没有回答楼主的“双重”问题 ;) - Antony Hatchkins

5

感谢回答。我觉得每个答案都有一部分的答案。这就是我想要的。

  1. ?符号只是(something|ε)的简写。因此,(a|ε)可以重写为a?。所以示例变成了:

    b*(abb*)*a?
    

    在Python中,我们会这样写:

    p = re.compile(r'^b*(abb*)*a?$')
    
  2. 将正则表达式语法直接翻译成Python(即复制和粘贴)不起作用的原因是,Python匹配最短的子字符串(如果没有$或^符号),而理论上的正则表达式匹配最长的初始子字符串。
    例如,如果我们有一个字符串:

    s = 'aa'
    

    我们的教科书正则表达式b*(abb*)*a?将无法匹配它,因为它有两个a。但是如果我们直接复制到Python中:

    >> p = re.compile(r'b*(abb*)*a?')
    >> bool(p.match(s))
    True
    

    这是因为我们的正则表达式仅匹配字符串“aa”的子字符串'a'。为了告诉Python对整个字符串进行匹配,我们必须告诉它字符串的开头和结尾,分别使用^$符号:

    >> p = re.compile(r'^b*(abb*)*a?$')
    >> bool(p.match(s))
    False
    

    请注意,Python的正则表达式match()在字符串开头进行匹配,因此它自动假定在开头有^。但是,search()函数不会这样做,因此我们保留^
    例如:

    >> s = 'aa'
    >> p = re.compile(r'b*(abb*)*a?$')
    >> bool(p.match(s))
    False                 # 正确
    >> bool(p.search(s))
    True                  # 不正确 - search忽略了第一个'a'
    

回答的总结非常出色! - Brian Postow
"...python匹配最短的子字符串..."是错误的。它只是不一定像数学上正确的正则表达式那样匹配最长的子字符串。 - Alan Moore
顺便提一下,这种“正则表达式导向”的行为并不是Python所特有的。所有所谓的Perl兼容版本都是如此:Perl、PHP、Ruby、JavaScript、Java、.NET等。(http://www.regular-expressions.info/engine.html) - Alan Moore
@Alan:是的,Python可以“匹配”aaa,但它无法匹配“aaab”。我的意思是Python将匹配最短可能的匹配。呃...或者Python将匹配任何子字符串,如果它与模式匹配。但是感谢您指出,我的措辞不够完美。 - Andriy Drozdyuk
实际上,一个数学上正确的正则表达式不会匹配“最长”的子字符串。它要么匹配整个字符串,要么就不匹配。没有“它匹配了大部分字符串…”这样的情况,这就是为什么我总是感到困惑。我认为,在编程语言中,匹配最长的子字符串比匹配最短的更有用…我的意思是,如果你想在babaaaaaaa中匹配'a*',你不想在b之前找到空字符串…如果你在aaaabaa中搜索,你也不想在第一个a之前找到空字符串,对吧? - Brian Postow
显示剩余2条评论

3

1

  • 使用bool(p.match('aa'))检查正则表达式是否匹配

  • p = re.compile('b*(abb*)*a?$')

  • \b匹配字符串边界;放置在\w\W之间(单词字符和非单词字符)

2

正则表达式在Python中是相当标准的。然而,每种语言都有一些自己的特点,它们并不是100%可移植的。在使用任何特定语言的正则表达式之前,您需要查阅一些细微的差异。

补充说明

在Python中,\epsilon没有特殊符号。它是一个空字符集。

在您的示例中,a|\epsilon等同于(a|)或者只是a?。之后必须使用$来匹配字符串的结尾。


我认为OP不想要单词边界...你可以在单词中间使用epsilon...它只是表示空字符串...另外,通过“标准”,我认为OP指的是计算理论教科书中使用的正则表达式类型...没有.或^$或\w或[1-9]或{3},但有\epsilon、\lambda等。 - Brian Postow
我不确定你所说的“这就是为什么在书籍中他们会发明一些特殊字符,你预期要在使用任何特定语言之前进行查找”的意思,请澄清/重新表述,我将接受。 - Andriy Drozdyuk
那是一个快速的猜测。我已经很久没有学习理论正则表达式了。删除。算了 :) - Antony Hatchkins

3
我不确定Python中的match是如何工作的,但我认为您可能需要在正则表达式中添加^....$。正则表达式匹配通常匹配子字符串,并找到最大匹配项,在p.match('aa')的情况下,这是"a"(可能是第一个)。 ^...$确保您匹配整个字符串,我相信这是您想要的。
理论/标准正则表达式假定您始终匹配整个字符串,因为您使用它来定义匹配的字符串语言,而不是在输入字符串中查找子字符串。

这里不需要使用^。在re.match中,它是默认的。而在re.search中,它不是默认的,这是两者之间唯一的区别。 - Antony Hatchkins
有趣的是,$ 是必要的吗?因为如果不是,你的正则表达式需要是 ...(a$|$),否则它会匹配任何带有 a 的内容... - Brian Postow
$ 表示行末,我认为这不是你要找的。re.match 已经可以使用 ^(表示行开头)实现此功能。 - jathanism
Pierre的例子似乎不同,暗示着^$实际上是必要的...而且^$通常并不意味着行尾和行首,它们意味着字符串的结尾和开头... - Brian Postow
匹配已经强制从字符串的开头开始,但不强制结束,因此您应该使用 $ - João Portela
啊,好的,所以$是必要的,但^不是。明白了。 - Brian Postow

1

你的正则表达式匹配成功是因为它匹配了任何样本文本的零宽度段落。你需要锚定你的正则表达式。这里有一种方法,使用零宽度前瞻断言:

re.compile(r'^(a(?!a)|b)*$')

1

你的表达式问题在于它匹配了空字符串,这意味着如果你执行以下操作:

>>> p = re.compile('b*(abb*)*(a|)')
>>> p.match('c').group(0)
''

而且,由于 re.match 尝试匹配字符串的开头,因此您必须告诉它匹配到字符串的末尾。只需使用 $ 即可。

>>> p = re.compile(r'b*(abb*)*(a|)$')
>>> print p.match('c')
None
>>> p.match('ababababab').group(0)
'ababababab'

PS- 您可能已经注意到我使用了 r'pattern' 而不是 'pattern',更多内容请参见这里(第一段)


1

你的第二个正则表达式应该是 epsilon 的适当替代品,就我所知,虽然我以前从未在正则表达式中看到过 epsilon。

值得一提的是,你的模式匹配了 'a'。也就是说,它匹配了:

  • 零个或多个 "b"(选择零个)
  • 零个或多个 "(abb*)"(选择零个)
  • 一个 "a" 或单词结尾(选择一个 a)。

正如 Jonathan Feinberg 指出的那样,如果你想确保整个字符串匹配,你必须锚定你的正则表达式的开头('^')和结尾('$')。在构建 Python 正则表达式时,你还应该使用原始字符串:r'my regex'。这将防止过多的反斜杠转义混淆。


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