import re
code = r"""
print(", ,", "l") # comment spaces
print(", ,", "l") # comment spaces
"""
print(re.sub(r"""
[Rr]("(?!"")|'(?!'')|"{3}|'{3}).*?\1 # Raw strings
| ("(?!"")|'(?!'')|"{3}|'{3})(?:\\.|[^\\])*?\2 # Normal strings
| [ \t]*\#[^\r\n]* # Comments
| ^[ \t]+ # Leading whitespace
| [ \t]+$ # Trailing whitespace
| ([ \t]{2,}) # Duplicate whitespace
""",
lambda m: m.group(0) if m.group(3) is None else ' ',
code,
flags = re.VERBOSE | re.DOTALL | re.MULTILINE))
输出:
print(", ,", "l") # comment spaces
print(", ,", "l") # comment spaces
http://ideone.com/3ouQee
我们需要匹配并丢弃所有其他模式,因此我们不会得到任何误报。唯一可能出现双空格的地方(我们不想替换它)是在字符串内部、在 cmets 中以及在行的开头结尾处。
模式的第一行匹配 Python 原始字符串,包括单引号和双引号,正常和多行。允许使用所有字符,但开始字符串的引号序列除外。
第二行匹配普通的 Python 字符串(非原始),单引号和双引号,普通和多行。跳过转义的 (\") 字符。否则,除反斜杠 (\) 和开始字符串的引号序列之外的所有字符。
第三行匹配 cmets 和任何前导空格。
第四行和第五行匹配前导和尾随空格。如果您还想修剪尾随空格,可以删除第五行。
最后一行匹配我们真正想要替换的;连续两个或多个空格。
替换不是一个字符串,而是一个 lambda 函数,用于检查第三组是否存在(模式中的最后一行)。如果不是,则将其替换为完整匹配(无变化)。如果该组存在,则将其替换为单个空格。