5

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

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):
    ...

我想\$ \(用一些自定义的速记“常量”替换所有重复序列 ( ),如下所示:

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):
    ...

有没有办法用正则表达式本身来做到这一点(即不使用 Python 的字符串格式来替换\BEGIN\$\(

澄清: Python 源代码纯粹是为了上下文和说明。我正在寻找 RE 解决方案,它可以在某些 RE 方言中使用(可能不是 Python 的方言),而不是专门针对 Python 的解决方案。

4

1 回答 1

9

我认为这在 Python 的正则表达式中是不可能的。您将需要仅由 PCRE 支持的递归(或者更确切地说是模式重用)。事实上,PCRE 甚至在其手册页中提到了如何定义速记(搜索“定义子模式”)。

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

/(\d\d)-(?1)-(?1)/

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

/(?<my>\d\d)-(?&my)-(?&my)/

现在我们已经很接近了,但是定义也是模式的第一次使用,这使表达式有些混乱。诀窍是首先在从未评估过的位置使用模式。手册页提出了一个依赖于(不存在的)组的条件DEFINE

/
(?(DEFINE)
  (?<my>\d\d)
)
(?&my)-(?&my)-(?&my)
/x

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

并且替代是一个负前瞻,它永远不会到达定义表达式的点:

/
(?!
  (?!)     # 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

但是,如果您没有条件,但确实有命名模式重用(而且我认为不存在这样的风格),您才真正需要这种形式。另一种变体的优点是,使用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

这种方法有两个主要的警告:

  1. 递归引用是原子的。也就是说,一旦引用匹配了某些内容,它将永远不会被回溯到。在某些情况下,这可能意味着您必须在制作表达式时有点聪明,以便第一个匹配项始终是您想要的匹配项。
  2. 您不能在定义的模式内使用捕获。如果您使用类似的东西(?<myPattern>a(b)c)并重用它,b则永远不会被捕获-重用模式时,所有组都不会被捕获。

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

于 2013-08-09T16:40:53.007 回答