【问题标题】:Is there a way to define custom shorthands in regular expressions?有没有办法在正则表达式中定义自定义简写?
【发布时间】:2013-08-10 00:46:33
【问题描述】:

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

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

【问题讨论】:

  • 不,抱歉。字符串格式有什么问题?
  • 嗯,不是“可移植的”,因为生成的带有自定义简写的正则表达式可以用于不同的编程语言。
  • 许多正则表达式方言不支持冗长的正则表达式。或断言。
  • 在 PCRE 中是可能的,但我猜您正在寻找 Python 解决方案?
  • @HamZa 我认为这只是 OP 想象的重用语法可能是什么样子的一个例子。

标签: regex


【解决方案1】:

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

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

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

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

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

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

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

如果之前使用了组 group,则构造 (?(group)true|false) 应用模式 true,否则使用(可选)模式 false。由于没有组DEFINE,因此条件将始终为假,并且将跳过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. 您不能在定义的模式内使用捕获。如果您使用 (?&lt;myPattern&gt;a(b)c) 之类的东西并重用它,b 将永远不会被捕获 - 重用模式时,所有组都不会被捕获。

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

【讨论】:

猜你喜欢
  • 2013-02-26
  • 1970-01-01
  • 2015-10-28
  • 2016-09-13
  • 2020-12-29
  • 2021-08-28
  • 2021-07-21
  • 1970-01-01
  • 2019-11-28
相关资源
最近更新 更多