【问题标题】:Reuse part of a Regex pattern重用正则表达式模式的一部分
【发布时间】:2013-11-16 15:48:03
【问题描述】:

考虑这个(非常简化的)示例字符串:

1aw2,5cx7

如您所见,它是由逗号分隔的两个digit/letter/letter/digit 值。

现在,我可以将其与以下内容匹配:

>>> from re import match
>>> match("\d\w\w\d,\d\w\w\d", "1aw2,5cx7")
<_sre.SRE_Match object at 0x01749D40>
>>>

问题是,我必须写两次\d\w\w\d。对于小模式,这还不错,但是对于更复杂的正则表达式,两次编写完全相同的东西会使最终模式变得庞大且难以处理。也显得多余。

我尝试使用命名捕获组:

>>> from re import match
>>> match("(?P<id>\d\w\w\d),(?P=id)", "1aw2,5cx7")
>>>

但它不起作用,因为它正在寻找两次出现的 1aw2,而不是 digit/letter/letter/digit

有什么方法可以保存部分模式,例如\d\w\w\d,以便以后在相同的模式中使用?换句话说,我可以在一个模式中重用一个子模式吗?

【问题讨论】:

    标签: python regex


    【解决方案1】:

    不,当使用标准库re模块时,正则表达式patterns不能被'符号化'。

    当然,您总是可以通过重用 Python 变量来做到这一点:

    digit_letter_letter_digit = r'\d\w\w\d'
    

    然后使用字符串格式化来构建更大的模式:

    match(r"{0},{0}".format(digit_letter_letter_digit), inputtext)
    

    或者,使用 Python 3.6+ f-strings:

    dlld = r'\d\w\w\d'
    match(fr"{dlld},{dlld}", inputtext)
    

    我经常使用这种技术从可重用的子模式中组合更大、更复杂的模式。

    如果你准备安装一个外部库,那么regex project可以用regex subroutine call解决这个问题。语法(?&lt;digit&gt;) 重用了已使用(隐式编号)捕获组的模式:

    (\d\w\w\d),(?1)
    ^........^ ^..^
    |           \
    |             re-use pattern of capturing group 1  
    \
      capturing group 1
    

    您可以对 named 捕获组执行相同操作,其中 (?&lt;groupname&gt;...) 是命名组 groupname,而 (?&amp;groupname)(?P&amp;groupname)(?P&gt;groupname) 重复使用匹配的模式by groupname(后两种形式是为了与其他引擎兼容的替代方案)。

    最后,regex 支持 (?(DEFINE)...) 块来“定义”子例程模式,而无需它们在该阶段实际匹配任何内容。您可以在该构造中放置多个 (..)(?&lt;name&gt;...) 捕获组,以便稍后在实际模式中引用它们:

    (?(DEFINE)(?<dlld>\d\w\w\d))(?&dlld),(?&dlld)
              ^...............^ ^......^ ^......^
              |                    \       /          
     creates 'dlld' pattern      uses 'dlld' pattern twice
    

    明确一点:标准库re 模块不支持子程序模式。

    【讨论】:

    • @iCodez this other answer 是用命名组代替,比如(?'digitletters'\d\w\w\d),(?&amp;digitletters) 不是一种真正“符号化模式”并在正则表达式中分解它们的方法吗?如果是,也许您可​​以将其标记为已接受,否则人们会一直认为没有办法这样做。
    • @iago-lito:Python re 模块不支持递归模式。只有regex 可以。请注意,您无法在对他们未参与的答案的评论中 ping OP。
    • 啊,好的。感谢您的澄清:) 也许至少值得通知读者 PCRE 支持它?我来到这篇文章时并没有专门寻找 python 风格的正则表达式解决方案。
    • @iago-lito:我不太明白这一点。这个问题是关于 Python 和它的标准库 re 模块,而不是一般的正则表达式引擎。引擎之间有太多的变化,没有一种标准的正则表达式语法。你最好去像regular-expressions.info 这样专门跟踪the various different regex features and what implementations support which ones of those 的网站。
    【解决方案2】:

    注意:这将适用于 PyPi regex module,不适用于 re 模块。

    在您的情况下,您可以使用符号 (?group-number)

    (\d\w\w\d),(?1)
    

    相当于:

    (\d\w\w\d),(\d\w\w\d)
    

    请注意\w 包括\d。正则表达式将是:

    (\d[a-zA-Z]{2}\d),(?1)
    

    【讨论】:

    • 太糟糕了 :-( 这是一个 PCRE 功能,我以为 Python 可以识别它。
    • 对于命名的捕获组,使用(?&amp;name)。还支持替代形式 (?P&gt;name) 和 (?P&amp;name)regex 太棒了!
    • 是的! PyPI 上的regex 模块很棒!这个答案也很棒! +1
    【解决方案3】:

    我也遇到了同样的问题,写了this snippet

    import nre
    my_regex=nre.from_string('''
    a=\d\w\w\d
    b={{a}},{{a}}
    c=?P<id>{{a}}),(?P=id)
    ''')
    my_regex["b"].match("1aw2,5cx7")
    

    由于缺少更具描述性的名称,我将部分正则表达式命名为 abc

    访问它们就像{{a}} 一样简单

    【讨论】:

      【解决方案4】:
      import re
      digit_letter_letter_digit = re.compile("\d\w\w\d") # we compile pattern so that we can reuse it later
      all_finds = re.findall(digit_letter_letter_digit, "1aw2,5cx7") # finditer instead of findall
      for value in all_finds:
          print(re.match(digit_letter_letter_digit, value))
      

      【讨论】:

        【解决方案5】:

        既然您已经在使用 re,为什么不使用字符串处理来管理模式重复:

        pattern = "P,P".replace("P",r"\d\w\w\d")
        
        re.match(pattern, "1aw2,5cx7")
        

        P = r"\d\w\w\d"
        
        re.match(f"{P},{P}", "1aw2,5cx7")
        

        【讨论】:

        • 那是相当不可读的。为什么不直接使用字符串替换?
        • @Martijn Pieters,你是对的。事实上,使用 re.sub() 并没有像我写的那样实际工作,因为正在处理正则表达式特殊字符,而不是简单地替换源。
        【解决方案6】:

        尝试使用反向引用,我相信它的工作原理类似于下面的匹配

        1aw2,5cx7
        

        你可以使用

        (\d\w\w\d),\1
        

        参考这里http://www.regular-expressions.info/backref.html

        【讨论】:

        • 感谢您的回答,但这在我的情况下实际上不起作用。使用\1 将使其查找两次出现的1aw2。我想要出现两次\d\w\w\d,不管数字/字母。
        • \1 back reference 匹配编号组匹配的文字。它不会重复使用该模式。
        猜你喜欢
        • 1970-01-01
        • 2012-05-17
        • 1970-01-01
        • 1970-01-01
        • 2018-08-24
        • 1970-01-01
        • 2018-11-29
        • 2018-11-10
        相关资源
        最近更新 更多