【问题标题】:Standard Regex vs python regex discrepancy标准正则表达式与 python 正则表达式差异
【发布时间】:2011-01-04 05:10:01
【问题描述】:

我正在读一本书,它们提供了一个如何将给定字符串与正则表达式匹配的示例。 这是他们的例子:

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

【问题讨论】:

  • 示例的注释是错误的......正则表达式不仅匹配没有连续 a 的 a 和 b 的字符串。我会用 b+ btw 替换 bb*。
  • @S.Lott:一个叫Kleene的家伙;见en.wikipedia.org/wiki/Stephen_Kleene ...“他还发明了正则表达式”
  • Epsilon 是“标准”Kleene reg exps 中的空字符串。正则表达式早在进入编程语言之前就来自计算理论(数学)......
  • @S.Lott:尝试阅读 OP 写的内容:“做正则表达式的理论或标准方法”。当然不是具体的实现。他在询问为什么他的 Python 解释(其中一个是正确的(只是省略了 epsilon))与他在书中看到的不符。这就是我们应该帮助的。答案是该表达式还匹配一个零长度字符串,它需要添加 $(或者,更好的 \Z)才能获得所需的效果。关于三角学书中的公式和尝试用 Python 实现它的问题可以吗?
  • @S.Lott。并非所有标准都来自标准编写组织。此外,并非所有标准事物都是软件工程意义上的标准。哪个标准组织决定莱布尼茨符号将成为“标准微积分符号”?任何学习过计算理论课程的人都应该知道做正则表达式的“正常”方式是什么:使用 Kleene 表示法。此外,Kleene 早于标准组织的概念。

标签: python regex theory


【解决方案1】:

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

  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 正则表达式 ma​​tch() 匹配字符串的 开头,因此它会自动假定 ^ 在开头。但是 search() 函数没有,因此我们保留 ^。
    比如:

    >> s = 'aa'
    >> p = re.compile(r'b*(abb*)*a?$')
    >> bool(p.match(s))
    False                 # Correct
    >> bool(p.search(s))
    True                  # Incorrect - search ignored the first 'a'
    

【讨论】:

  • 优秀的答案总结!
  • "...python 匹配最短的子字符串..." 是错误的。它只是不一定匹配最长的子字符串,就像数学上正确的正则表达式那样。
  • @Alan:如果没有提供 ^ 或 $,则匹配最短的子字符串。
  • 考虑应用于aaabb 的正则表达式a+|a+b*。最短的匹配是a,最长的匹配是整个字符串,但Python匹配aaa。它尝试各种方法来查找匹配项,由正则表达式的编写方式决定,并在找到有效的方法后立即停止。如果您颠倒替代的顺序 - a+b*|a+ - 它将匹配整个字符串。如果您添加一个锚点 - (a+|a+b*)$ - 第一个替代方案失败,因为它没有到达终点,但第二个替代方案成功。不是最短的匹配,只是第一个。
  • 顺便说一下,这种“正则表达式导向”的行为并不是 Python 独有的。所有所谓的与 Perl 兼容的风格都这样做:Perl、PHP、Ruby JavaScript、Java、.NET 等。(regular-expressions.info/engine.html)
【解决方案2】:

实际上,这个例子工作得很好......到一个小细节。我会写:

>>> 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 而没有重复 a。如果确实要检查整个字符串,则需要稍作更改:

>>> p = re.compile('^b*(abb*)*a?$')
>>> m = p.match('aa')
>>> print m
None

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

最后,您可以使用第一个正则表达式来组合这两种方法,但在最后进行测试:

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

补充:对于 OT 的第二部分,在我看来,标准正则表达式和 python 实现之间没有差异。当然,符号略有不同,python 实现建议一些扩展(与大多数其他包一样)。

【讨论】:

  • +1 击败了我的答案! :) 顺便说一句,^ 不是强制性的,因为 re.match() 只在字符串的开头尝试模式。
  • 哦..你的例子是错误的。 p = re.compile('b*(abb)*a?') 不匹配 'aba'
  • 哎呀..只是忘记了第一个正则表达式中的星号...已更正!
  • 但您没有回答 OP 的“双重”问题;​​)
【解决方案3】:

1

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

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

  • \b 匹配字符串的边框;位于\w 和\W 之间(单词字符和非单词字符)

2

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

加法

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

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

【讨论】:

  • 我不认为 OP 想要一个单词边界......你可以在单词中间使用 epsilon......它只是意味着空字符串......另外,通过“标准”我认为 OP 意味着计算教科书理论中使用的那种正则表达式......没有 .或 ^$ 或 \w 或 [1-9] 或 {3},但使用 \epsilon、\lambda 等
  • 我不确定您所说的“这就是为什么在书中他们发明了一些特殊字符,您需要在使用任何特定语言之前查找这些字符。”。请澄清/重写,我会接受。
  • 这是一个快速的猜测。我学习理论正则表达式已经很久了。已移除。算了:)
【解决方案4】:

您的表达式的问题在于它与空字符串匹配,这意味着如果您这样做:

>>> 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-你可能已经注意到我在here(第一段)上更多地使用了 r'pattern' 而不是 'pattern'

【讨论】:

    【解决方案5】:

    据我所知,您的第二个 re 应该是 epsilon 的合适替代品,尽管我以前从未在正则表达式中看到 epsilon。

    不管怎样,你的模式匹配“a”。也就是说,是匹配的:

    • 零个或多个“b”(选择零)
    • 零个或多个“(abb*)”(选择零)
    • 一个“a”或词尾(选择 a)。

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

    【讨论】:

      【解决方案6】:

      您正在匹配,因为您的正则表达式匹配任何样本文本的任何零宽度段。您需要锚定您的正则表达式。这是使用零宽度前瞻断言的一种方法:

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

      【讨论】:

        【解决方案7】:

        我不确定 match 在 python 中是如何工作的,但我认为您可能需要将 ^....$ 添加到您的 RE。 RegExp 匹配通常匹配子字符串,它会找到最大的匹配,在 p.match('aa') 的情况下是“a”(可能是第一个)。 ^...$ 确保您匹配整个字符串,我相信这是您想要的。

        理论/标准正则表达式假设您始终匹配整个字符串,因为您使用它来定义匹配的字符串语言,而不是在输入字符串中找到子字符串。

        【讨论】:

        • ^ 在这里不是必需的。假设在 re.match 中。在研究中,这不是两者之间的唯一区别。
        • 有趣的是需要 $ 吗?因为如果不是,你的正则表达式需要是 ...(a$|$) 否则它匹配任何带有 a 的东西...
        • $ 代表一行结束,我认为这不是你要找的。 re.match 已经与 ^ 一样(用于行开头)。
        • Pierre 的例子似乎有所不同,暗示 ^$ 实际上是必要的……而 ^$ 通常不表示 LINE 的结束和开始,它们表示 STRING 的结束和开始...跨度>
        • match 已经强制匹配从字符串的开头开始但不强制结束,所以你应该使用$
        猜你喜欢
        • 2010-09-12
        • 2011-08-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-08-18
        • 2019-11-19
        • 1970-01-01
        • 2015-12-29
        相关资源
        最近更新 更多