Python re.findall()不会终止

时间:2014-01-15 19:25:45

标签: python regex

下面我有一个相当复杂的正则表达式似乎永远不会终止。这不仅仅需要花费很长时间 - 我已经等了好几分钟才得到回应而没有运气。

下面是一些可以重现问题的代码:

import re    
link = re.compile(u'(?i)((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\((?:[^\s()<>]+|(?:\([^\s()<>]+\)))*\))+(?:\((?:[^\s()<>]+|(?:\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?\xab\xbb\u201c\u201d\u2018\u2019]))', re.IGNORECASE)
text = "Check out this link http://ru.wikipedia.org/wiki/%D0%9B%D0%BE%D0%B2%D0%B5%D1%86_%D1%81%D0%BD%D0%BE%D0%B2_(%D0%B0%D0%BC%D1%83%D0%BB%D0%B5%D1%82"
matches = re.findall(link, text)

这里是将re.DEBUG作为标志传递给模式的输出:

subpattern 1
  subpattern None
    branch
      literal 104
      literal 116
      literal 116
      literal 112
      max_repeat 0 1
        literal 115
      literal 58
      literal 47
      literal 47
    or
      literal 119
      literal 119
      literal 119
      max_repeat 0 3
        in
          category category_digit
      literal 46
    or
      max_repeat 1 4294967295
        in
          range (97, 122)
          range (48, 57)
          literal 46
          literal 45
      literal 46
      max_repeat 2 4
        in
          range (97, 122)
      literal 47
  max_repeat 1 4294967295
    subpattern None
      branch
        max_repeat 1 4294967295
          in
            negate None
            category category_space
            literal 40
            literal 41
            literal 60
            literal 62
      or
        literal 40
        max_repeat 0 4294967295
          subpattern None
            branch
              max_repeat 1 4294967295
                in
                  negate None
                  category category_space
                  literal 40
                  literal 41
                  literal 60
                  literal 62
            or
              subpattern None
                literal 40
                max_repeat 1 4294967295
                  in
                    negate None
                    category category_space
                    literal 40
                    literal 41
                    literal 60
                    literal 62
                literal 41
        literal 41
  subpattern None
    branch
      literal 40
      max_repeat 0 4294967295
        subpattern None
          branch
            max_repeat 1 4294967295
              in
                negate None
                category category_space
                literal 40
                literal 41
                literal 60
                literal 62
          or
            subpattern None
              literal 40
              max_repeat 1 4294967295
                in
                  negate None
                  category category_space
                  literal 40
                  literal 41
                  literal 60
                  literal 62
              literal 41
      literal 41
    or
      in
        negate None
        category category_space
        literal 96
        literal 33
        literal 40
        literal 41
        literal 91
        literal 93
        literal 123
        literal 125
        literal 59
        literal 58
        literal 39
        literal 34
        literal 46
        literal 44
        literal 60
        literal 62
        literal 63
        literal 171
        literal 187
        literal 8220
        literal 8221
        literal 8216
        literal 8217

2 个答案:

答案 0 :(得分:3)

如果你从字符串末尾删除%D0%B5%D1%82,它就会起作用(尽管大约是10秒)。随着您增加字符串和匹配的复杂性,您将指数增加检查表达式条件所需的处理量,并且您可能会将CPU一直加速到100%。

这被称为catastrphic backtracking - 它让我觉得你有99到100个问题;)

enter image description here

CPU被阻止到无法继续处理表达式的程度。

<强>解决方案:

  • 简化您的表达(更可靠和明智的选择),或
  • 在尝试验证之前缩短/截断样本(首先取消使用表达式的目的)

老实说,我不知道这是否会为你削减它,但由于你有很多重叠的条件,这似乎会削减它们。最好检查一堆样本来验证:

(?i)(?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)((?:\(?[^\s()<>]+\)?)*[^\s`!()\[\]{};:\'".,<>?\xab\xbb\u201c\u201d\u2018\u2019])

示例:http://regex101.com/r/lV0oL4

我相信你可以进一步简化它,这只是一个快速的尝试。字符串的处理时间:小于100毫秒。

答案 1 :(得分:1)

这可能无济于事。如果Python支持积极的量词,我不知道(但不要这么认为) 但如果确实如此,下面就会使其免于灾难性的回溯 我试图制作一个相当于停止回溯而没有积极的东西 确实如此,但意义发生了变化,并没有达到相同的效果。

你的正则表达式是每个细节的模拟递归正则表达式。您可以在下面看到重复的模式 这是这里的主要问题,因为它必须具有积极性,因为在现实中,它匹配可选的平衡文本,并且没有限制阻止它回溯。

在这种情况下,我认为没有办法获得一个等效(非回溯)正则表达式。
因此,如果它不能做有量的量词,那么正则表达式在其使用的任何上下文中都注定要失败。

祝你好运!
如果你使用RegexFormat4,那么注意力巨大的正则表达式就是孩子们玩耍。它是一个cut'n粘贴和按钮单击。修改它,压缩它,测试它。使用不同的结构重复几次。

 #  (?i)((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\((?:[^\s()<>]+|(?:\([^\s()<>]+\)))*+\))+(?:\((?:[^\s()<>]+|(?:\([^\s()<>]+\)))*+\)|[^\s`!()\[\]{};:\'".,<>?\xab\xbb\u201c\u201d\u2018\u2019]))

 #  matches:  'http://ru.wikipedia.org/wiki/%D0%9B%D0%BE%D0%B2%D0%B5%D1%86_%D1%81%D0%BD%D0%BE%D0%B2_'

 (?i)
 (
      (?:
           https?://
        |  www \d{0,3} [.] 
        |  [a-z0-9.\-]+ [.] [a-z]{2,4} /
      )
      (?:
           [^\s()<>]+ 
        |  \(
           (?:
                [^\s()<>]+ 
             |  (?: \( [^\s()<>]+ \) )
           )*+                           #  <- Possesive
           \)
      )+
      (?:
           \(
           (?:
                [^\s()<>]+ 
             |  (?: \( [^\s()<>]+ \) )
           )*+                           #  <- Possesive
           \)
        |  [^\s`!()\[\]{};:\'".,<>?\xab\xbb\u201c\u201d\u2018\u2019] 
      )
 )