有可能在打印前清理一个冗长的python正则表达式吗?

时间:2016-02-26 00:03:35

标签: 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"

问题:

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

可能的解决方案:

This question ("strip a verbose python regex")似乎与我要求的非常接近,而且是answered。但那是几年前的事情,所以我想知道是否找到了新的(最好是内置的)解决方案。

除了上述内容之外,还有一些解决方法,例如使用隐式字符串连接,然后使用.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"

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

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

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

背景

我问,因为我想建议编辑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'

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

2 个答案:

答案 0 :(得分:3)

以下测试过的脚本包含一个功能,可以很好地将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))

这个函数是用pcre-8 / pcre2-10 xmode语法编写的正则表达式。

它保留[character classes](?#comment groups)\Q...\E文字文字范围内的空白。

RegexTidy

上面的decomment正则表达式是我即将发布的RegexTidy应用程序中使用的一个变体,它不仅会解决上面显示的正则表达式(这很漂亮)很容易做到,但它也会走另一条路并且 Tidy 一个正则表达式 - 即将它从非xmode正则表达式转换为xmode语法,向嵌套组添加空格缩进以及添加注释(更难)。

P.S。在给出这个答案之前,一般原则是因为它使用的是一个长于几行的正则表达式,请添加一个描述一个未正确处理的示例的注释。干杯!

答案 1 :(得分:1)

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

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

[(IN, [(RANGE, (48, 57))]), (IN, [(RANGE, (65, 90))]), (IN, [(RANGE, (97, 121))]), (LITERAL, 122)]

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