是否可以在打印之前清理详细的 python 正则表达式?

dth*_*hor 5 python regex python-3.x

设置:

假设我在脚本中定义了以下正则表达式。我想保留这些评论以备将来使用,因为我很健忘。

RE_TEST = re.compile(r"""[0-9]            # 1 Number
                         [A-Z]            # 1 Uppercase Letter
                         [a-y]            # 1 lowercase, but not z
                         z                # gotta have z...
                         """,
                     re.VERBOSE)

print(magic_function(RE_TEST))   # returns: "[0-9][A-Z][a-y]z"
Run Code Online (Sandbox Code Playgroud)

问题:

Python(3.4+)是否有办法将其转换为简单的字符串"[0-9][A-Z][a-y]z"

可能的解决方案:

这个问题(“strip a verbose python regex”)似乎与我的要求非常接近,并且得到了回答。但那是几年前的事了,所以我想知道是否找到了新的(最好是内置的)解决方案。

除了上述之外,还有一些变通方法,例如使用隐式字符串连接,然后使用.pattern属性:

RE_TEST = re.compile(r"[0-9]"      # 1 Number
                     r"[A-Z]"      # 1 Uppercase Letter
                     r"[a-y]"      # 1 lowercase, but not z
                     r"z",         # gotta have z...
                     re.VERBOSE)

print(RE_TEST.pattern)    # returns: "[0-9][A-Z][a-y]z"
Run Code Online (Sandbox Code Playgroud)

或者只是单独评论模式而不编译它:

# matches pattern "nXxz"
RE_TEST = "[0-9][A-Z][a-y]z"
print(RE_TEST)
Run Code Online (Sandbox Code Playgroud)

但我真的很想保持编译后的正则表达式(第一个示例)。也许我正在从某个文件中提取正则表达式字符串,并且该文件已经在使用详细形式。

背景

我问是因为我想建议对该unittest模块进行编辑。

现在,如果您assertRegex(string, pattern)使用带有注释的编译模式运行并且该断言失败,那么打印的输出有点难看(下面是一个虚拟的正则表达式):

Traceback (most recent call last):
  File "verify_yaml.py", line 113, in test_verify_mask_names
    self.assertRegex(mask, RE_MASK)
AssertionError: Regex didn't match: '(X[1-9]X[0-9]{2})      # comment\n                         |(XXX[0-9]{2})         # comment\n                         |(XXXX[0-9E])          # comment\n                         |(XXXX[O1-9])          # c
omment\n                         |(XXX[0-9][0-9])       # comment\n                         |(XXXX[
1-9])           # comment\n                         ' not found in 'string'
Run Code Online (Sandbox Code Playgroud)

我将建议assertRegexassertNotRegex方法在打印正则表达式之前通过删除注释和额外的空格或通过不同的方式打印它来清理正则表达式。

rid*_*ner 5

以下经过测试的脚本包含一个可以很好地将 xmode 正则表达式字符串转换为非 xmode 的函数:

pcre_detidy(retext)

# Function pcre_detidy to convert xmode regex string to non-xmode.
# Rev: 20160225_1800
import re
def detidy_cb(m):
    if m.group(2): return m.group(2)
    if m.group(3): return m.group(3)
    return ""

def pcre_detidy(retext):
    decomment = re.compile(r"""(?#!py/mx decomment Rev:20160225_1800)
        # Discard whitespace, comments and the escapes of escaped spaces and hashes.
          ( (?: \s+                  # Either g1of3 $1: Stuff to discard (3 types). Either ws,
            | \#.*                   # or comments,
            | \\(?=[\r\n]|$)         # or lone escape at EOL/EOS.
            )+                       # End one or more from 3 discardables.
          )                          # End $1: Stuff to discard.
        | ( [^\[(\s#\\]+             # Or g2of3 $2: Stuff to keep. Either non-[(\s# \\.
          | \\[^# Q\r\n]             # Or escaped-anything-but: hash, space, Q or EOL.
          | \(                       # Or an open parentheses, optionally
            (?:\?\#[^)]*(?:\)|$))?   # starting a (?# Comment group).
          | \[\^?\]? [^\[\]\\]*      # Or Character class. Allow unescaped ] if first char.
            (?:\\[^Q][^\[\]\\]*)*    # {normal*} Zero or more non-[], non-escaped-Q.
            (?:                      # Begin unrolling loop {((special1|2) normal*)*}.
              (?: \[(?::\^?\w+:\])?  # Either special1: "[", optional [:POSIX:] char class.
              | \\Q       [^\\]*     # Or special2: \Q..\E literal text. Begin with \Q.
                (?:\\(?!E)[^\\]*)*   # \Q..\E contents - everything up to \E.
                (?:\\E|$)            # \Q..\E literal text ends with \E or EOL.
              )        [^\[\]\\]*    # End special: One of 2 alternatives {(special1|2)}.
              (?:\\[^Q][^\[\]\\]*)*  # More {normal*} Zero or more non-[], non-escaped-Q.
            )* (?:\]|\\?$)           # End character class with ']' or EOL (or \\EOL).
          | \\Q       [^\\]*         # Or \Q..\E literal text start delimiter.
            (?:\\(?!E)[^\\]*)*       # \Q..\E contents - everything up to \E.
            (?:\\E|$)                # \Q..\E literal text ends with \E or EOL.
          )                          # End $2: Stuff to keep.
        | \\([# ])                   # Or g3of3 $6: Escaped-[hash|space], discard the escape.
        """, re.VERBOSE | re.MULTILINE)
    return re.sub(decomment, detidy_cb, retext)

test_text = r"""
        [0-9]            # 1 Number
        [A-Z]            # 1 Uppercase Letter
        [a-y]            # 1 lowercase, but not z
        z                # gotta have z...
        """
print(pcre_detidy(test_text))
Run Code Online (Sandbox Code Playgroud)

此函数确定以 pcre-8/pcre2-10 xmode 语法编写的正则表达式。

它保留内部的空格和[character classes]文字文本跨度。(?#comment groups)\Q...\E

正则表达式整洁

上面的正则decomment表达式是我在即将发布的、尚未发布的RegexTidy应用程序中使用的一个变体,它不仅会确定如上所示的正则表达式(这很容易做到),而且还会转到另一个正则表达式方式并整理正则表达式 - 即,将其从非 xmode 正则表达式转换为 xmode 语法,向嵌套组添加空格缩进以及添加注释(这更难)。

ps 在对这个答案投反对票之前,因为它使用的正则表达式长于几行,请添加一条注释,描述一个未正确处理的示例。干杯!


Jas*_*n S 2

通过查看处理此问题的方式sre_parse,实际上没有任何一点可以将详细的正则表达式“转换”为常规正则表达式然后进行解析。相反,您的详细正则表达式将直接输入到解析器,其中标志的存在VERBOSE使其忽略字符类外部的未转义空白,并且如果它不在字符类或捕获组#内,则从未转义到行尾(文档中缺少该内容)。

解析详细正则表达式的结果不是"[0-9][A-Z][a-y]z"。相反,它是:

[(IN, [(RANGE, (48, 57))]), (IN, [(RANGE, (65, 90))]), (IN, [(RANGE, (97, 121))]), (LITERAL, 122)]
Run Code Online (Sandbox Code Playgroud)

为了正确地将详细的正则表达式转换为"[0-9][A-Z][a-y]z"您可以自己解析它。您可以使用像pyparsing. 您的问题中链接的另一个答案使用正则表达式,它通常不会正确复制行为(特别是字符类内的空格和捕获组/字符类内的 # 。即使只是处理转义也不像使用好的解析器那么方便。 )