有没有办法在正则表达式中定义自定义短序?

Mic*_*kov 5 regex

我有一个表格的正则表达式

def parse(self, format_string):
    for m in re.finditer(
        r"""(?: \$ \( ( [^)]+ ) \) )   # the field access specifier
          | (
                (?:
                    \n | . (?= \$ \( ) # any one single character before the '$('
                )
              | (?:
                    \n | . (?! \$ \( ) # any one single character, except the one before the '$('
                )*
            )""",
        format_string,
        re.VERBOSE):
    ...
Run Code Online (Sandbox Code Playgroud)

我想\$ \(用一些自定义简写"常量" 替换所有重复序列(),如下所示:

def parse(self, format_string):
    re.<something>('\BEGIN = \$\(')
    for m in re.finditer(
        r"""(?: \BEGIN ( [^)]+ ) \) )   # the field access specifier
          | (
                (?:
                    \n | . (?= \BEGIN ) # any one single character before the '$('
                )
              | (?:
                    \n | . (?! \BEGIN ) # any one single character, except the one before the '$('
                )*
            )""",
        format_string,
        re.VERBOSE):
    ...
Run Code Online (Sandbox Code Playgroud)

有没有一种方法(即不使用Python的字符串格式化来代替用正则表达式本身做这个\BEGIN\$\()?

澄清: Python源代码纯粹用于上下文和插图.我正在寻找RE解决方案,它可以在一些RE方言中使用(可能不是Python的方言),而不是专门针对Python的解决方案.

Mar*_*der 9

我不认为这在Python的正则表达式中是可行的.您需要递归(或者说模式重用),这只是PCRE支持的.事实上,PCRE甚至提到了如何在其手册页中定义缩写词(搜索"定义子模式").

在PCRE中,您可以以类似于反向引用的方式使用递归语法 - 除了再次应用模式,而不是尝试从反向引用获取相同的文本文本.例:

/(\d\d)-(?1)-(?1)/
Run Code Online (Sandbox Code Playgroud)

匹配日期之类的东西((?1)将被替换为,\d\d并再次评估).这非常强大,因为如果你在引用的组本身中使用这个构造,你会得到递归 - 但我们甚至不需要这里.以上也适用于命名组:

/(?<my>\d\d)-(?&my)-(?&my)/
Run Code Online (Sandbox Code Playgroud)

现在我们已经非常接近,但是这个定义也是该模式的第一次使用,这有点混淆了表达式.诀窍是首先在永不评估的位置使用模式.手册页建议一个依赖于(不存在的)组的条件DEFINE:

/
(?(DEFINE)
  (?<my>\d\d)
)
(?&my)-(?&my)-(?&my)
/x
Run Code Online (Sandbox Code Playgroud)

如果之前使用了组,则构造(?(group)true|false)应用模式,true否则group使用(可选)模式false.由于没有组DEFINE,条件将始终为false,并且true将跳过该模式.因此,我们可以在那里放置各种定义,而不用担心它们会被应用并弄乱我们的结果.这样我们就可以将它们放入模式中,而无需使用它们.

替代方案是一种消极的前瞻,永远不会达到定义表达式的程度:

/
(?!
  (?!)     # fail - this makes the surrounding lookahead pass unconditionally
  # the engine never gets here; now we can write down our definitions
  (?<my>\d\d) 
)
(?&my)-(?&my)-(?&my)
/x
Run Code Online (Sandbox Code Playgroud)

但是,你只需要这个表单,如果你没有条件,但确实有命名模式重用(我不认为这样的味道存在).另一个变体的优点是,使用DEFINE它可以明确表示组的用途,而前瞻性的方法有点混淆.

回到你原来的例子:

/
# Definitions
(?(DEFINE)
  (?<BEGIN>[$][(])
)
# And now your pattern
  (?: (?&BEGIN) ( [^)]+ ) \) ) # the field access specifier
|
  (
    (?: # any one single character before the '$('
      \n | . (?= (?&BEGIN) ) 
    )
  | 
    (?: # any one single character, except the one before the '$('
      \n | . (?! (?&BEGIN) ) 
    )*
  )
/x
Run Code Online (Sandbox Code Playgroud)

这种方法有两个主要的注意事项:

  1. 递归引用是原子的.也就是说,一旦引用匹配某些东西,它将永远不会被回溯到.对于某些情况,这可能意味着您必须在制作表达方面有点聪明,以便第一场比赛永远是您想要的.
  2. 您无法在定义的模式中使用捕获.如果您使用类似的东西(?<myPattern>a(b)c)并重复使用它,b则永远不会捕获 - 当重用模式时,所有组都是非捕获的.

然而,与任何类型的插值或连接相比,最重要的优点是,您永远不会产生无效的模式,并且您也不会弄乱您的捕获组计数.

  • 此答案已添加到[Stack Overflow Regular Expression FAQ](http://stackoverflow.com/a/22944075/2736496)的"Control Verbs and Recursion"下. (2认同)