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

时间:2013-08-09 15:02:45

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

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

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的解决方案。

1 个答案:

答案 0 :(得分:9)

我不认为这在Python的正则表达式中是可行的。您需要递归(或者说模式重用),这只是PCRE支持的。实际上,PCRE甚至提到了如何定义短语在man page中的作用(搜索“定义子模式”)。

在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),则构造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

但是,你真的只需要这个表单,如果你没有条件,但确实有命名模式重用(我不认为这样的味道存在)。另一个变体的优点是,使用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. 递归引用是atomic。也就是说,一旦引用匹配某些东西,它将永远不会被回溯到。对于某些情况,这可能意味着你必须在制作表达方面有点聪明,这样第一场比赛将永远是你想要的。
  2. 您无法在定义的模式中使用捕获。如果您使用(?<myPattern>a(b)c)之类的内容并重复使用它,则永远不会捕获b - 重复使用模式时,所有组都不会被捕获。
  3. 与任何类型的插值或连接相比,最重要的优点是,您永远不会使用此方法生成无效模式,并且您也不会弄乱您的捕获组计数。