是否可以在打印之前清理冗长的 python 正则表达式?
Is is possible to clean a verbose python regex before printing it?
设置:
假设我在脚本中定义了以下正则表达式。我想把评论留在那里留给以后的我,因为我很健忘。
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") seems to be pretty close to what I'm asking for and it was 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'
我建议 assertRegex
和 assertNotRegex
方法在打印正则表达式之前清理正则表达式,方法是删除注释和额外的空格或以不同的方式打印它。
通过 sre_parse
处理此问题的方式,您的冗长正则表达式确实没有任何意义 "converted" 变成常规表达式然后进行解析。相反,您的冗长正则表达式被直接馈送到解析器,其中 VERBOSE
标志的存在使其忽略字符 classes 之外的未转义空格,以及从未转义的 #
到结尾-line 如果它不在字符 class 或捕获组 内(文档中缺少)。
解析冗长的正则表达式的结果不是 "[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
这样的库来做到这一点。您问题中链接的另一个答案使用正则表达式,它通常不会正确复制行为(具体来说,字符内的空格 classes 和 # inside capture groups/character classes。甚至只是处理转义不如好的解析器方便。)
以下经过测试的脚本包含一个函数,可以很好地将 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 : 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 : Stuff to discard.
| ( [^\[(\s#\]+ # Or g2of3 : 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 : Stuff to keep.
| \([# ]) # Or g3of3 : 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。在给出这个答案之前,因为它使用的正则表达式长于几行,所以在对一般原则投反对票之前,请添加一条评论来描述一个未正确处理的示例。干杯!
设置:
假设我在脚本中定义了以下正则表达式。我想把评论留在那里留给以后的我,因为我很健忘。
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") seems to be pretty close to what I'm asking for and it was 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'
我建议 assertRegex
和 assertNotRegex
方法在打印正则表达式之前清理正则表达式,方法是删除注释和额外的空格或以不同的方式打印它。
通过 sre_parse
处理此问题的方式,您的冗长正则表达式确实没有任何意义 "converted" 变成常规表达式然后进行解析。相反,您的冗长正则表达式被直接馈送到解析器,其中 VERBOSE
标志的存在使其忽略字符 classes 之外的未转义空格,以及从未转义的 #
到结尾-line 如果它不在字符 class 或捕获组 内(文档中缺少)。
解析冗长的正则表达式的结果不是 "[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
这样的库来做到这一点。您问题中链接的另一个答案使用正则表达式,它通常不会正确复制行为(具体来说,字符内的空格 classes 和 # inside capture groups/character classes。甚至只是处理转义不如好的解析器方便。)
以下经过测试的脚本包含一个函数,可以很好地将 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 : 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 : Stuff to discard.
| ( [^\[(\s#\]+ # Or g2of3 : 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 : Stuff to keep.
| \([# ]) # Or g3of3 : 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。在给出这个答案之前,因为它使用的正则表达式长于几行,所以在对一般原则投反对票之前,请添加一条评论来描述一个未正确处理的示例。干杯!