【问题标题】:finditer hangs when matching against long string匹配长字符串时 finditer 挂起
【发布时间】:2009-04-16 09:14:26
【问题描述】:

我有一个有点复杂的正则表达式,我试图匹配一个长字符串(65,535 个字符)。我正在寻找字符串中 re 的多次出现,因此我正在使用 finditer。它可以工作,但由于某种原因,它在识别出前几次出现后挂起。有谁知道这可能是为什么?这是sn-p的代码:

pattern = "(([ef]|([gh]d*(ad*[gh]d)*b))d*b([ef]d*b|d*)*c)"

matches = re.finditer(pattern, string)
for match in matches:
    print "(%d-%d): %s" % (match.start(), match.end(), match.group())

它打印出前四次出现,但随后挂起。当我使用 Ctrl-C 杀死它时,它告诉我它在迭代器中被杀死:

Traceback (most recent call last):
  File "code.py", line 133, in <module>
    main(sys.argv[1:])
  File "code.py", line 106, in main
    for match in matches:
KeyboardInterrupt

如果我用更简单的 re 试试,效果很好。

我在 Windows XP 上的 Cygwin 上运行的 python 2.5.4 上运行它。

我设法让它用一个非常短的字符串挂起来。使用这 50 个字符的字符串,大约 5 分钟后它再也没有返回:

ddddddeddbedddbddddddddddddddddddddddddddddddddddd

使用这 39 个字符的字符串大约需要 15 秒才能返回(并且不显示匹配项):

ddddddeddbedddbdddddddddddddddddddddddd

使用这个字符串,它会立即返回:

ddddddeddbedddbdddddddddddddd

【问题讨论】:

    标签: python regex performance


    【解决方案1】:

    绝对是指数行为。您的正则表达式中有很多 d* 部分,以至于当它到达长串 d 时会疯狂地回溯,但无法更早地匹配某些内容。您需要重新考虑正则表达式,因此可以尝试的路径更少。

    我特别认为:

    ([ef]d\*b|d\*)*</pre></code> and <code><pre>([ef]|([gh]d\*(ad\*[gh]d)\*b))d\*b
    

    可能需要重新考虑,因为他们会强制重试备用比赛。此外,它们在匹配方面也有重叠。例如,它们都会匹配 edb,但如果其中一个失败并尝试回溯另一部分,则可能会有相同的行为。

    因此,简而言之,尽量不要使用|,并尽量确保模式不会重叠。

    【讨论】:

    • 谢谢——这很有帮助。我可以消除|只需有几个单独的正则表达式。我可能会问一个关于删除 d* 的新问题——我基本上希望正则表达式在任何位置接受 'd'。
    • @Ben:然后在测试模式之前从字符串中删除 d。
    • @Gumbo:啊!是的。我试过了。不幸的是,我需要知道匹配项在原始字符串中的确切位置。
    • @Ben:然后你可以提取每个不是d 的字符及其位置,运行没有ds 的正则表达式,然后检索旧位置。
    • 你可以在这个问题中做一个指向你下一个问题的链接,这样我们就可以正确回答了。有几种可能性,例如将字符串拆分为遇到 d 的几个子字符串。
    【解决方案2】:

    会不会是您的表达式触发了 Python RE 引擎中的指数行为?

    This article 处理这个问题。如果您有时间,您可能想尝试在使用这些想法开发的 RE 引擎中运行您的表达式。

    【讨论】:

    • 哇 - 多么棒的链接。很有意思。谢谢。
    • 谢谢。还有 pyre2 - re2 的 python 绑定,具有兼容接口pypi.org/project/pyre2
    【解决方案3】:

    感谢所有回复,这些回复非常有帮助。最后,令人惊讶的是,它很容易加快速度。这是原始的正则表达式:

    (([ef]|([gh]d*(ad*[gh]d)*b))d*b([ef]d*b|d*)*c)
    

    我注意到末尾的 |d* 并不是我真正需要的,所以我将其修改如下:

    (([ef]|([gh]d*(ad*[gh]d)*b))d*b([ef]d*bd*)*c)
    

    现在它几乎可以立即处理 65,536 个字符的字符串。我想现在我只需要确保正则表达式真的匹配我需要它匹配的字符串......

    【讨论】:

    • 接受你自己的答案无论如何都不会在 48 小时内起作用。 :-) +1 尽管如此。
    【解决方案4】:

    我认为您经历了所谓的“灾难性回溯”。

    您的正则表达式有许多可选/替代部分,所有这些部分仍在尝试匹配,因此先前的子表达式在本地失败时将字符返回给以下表达式。这会导致正则表达式中的来回行为和指数级增长的执行时间。

    Python(2.7+?,我不确定)支持原子分组和所有格量词,您可以检查您的正则表达式以识别应该匹配或整体失败的部分。这样就可以控制不必要的回溯。

    【讨论】:

      【解决方案5】:

      灾难性的回溯!

      正则表达式可能非常昂贵。某些(无意的和有意的)字符串可能会导致 RegEx 表现出指数行为。我们为此采取了几个修补程序。正则表达式非常方便,但开发人员确实需要了解它们是如何工作的;我们被他们咬了。

      示例和调试器:

      http://www.codinghorror.com/blog/archives/000488.html

      【讨论】:

        【解决方案6】:

        你已经给自己答案了:正则表达式是复杂和模棱两可的。

        您应该尝试找到一个更简单、更独特、更易于处理的表达式。或者告诉我们您想要完成什么,我们会尽力帮助您找到一个。


        编辑   如果您只想让ds 出现在您对John Montgomery’s answer 的评论中所说的每个位置,您应该在测试模式之前将其删除:

        import re
        
        string = "ddddddeddbedddbddddddddddddddddddddddddddddddddddd"
        pattern = "(([ef]|([gh](a[gh])*b))b([ef]b)*c)"
        matches = re.finditer(pattern, re.sub("d+", "", string))
        for match in matches:
            print "(%d-%d): %s" % (match.start(), match.end(), match.group())
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-12-14
          • 2017-06-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多