我正在将一个解析工具从Perl移植到Python:
my $lineno = 1;
my @data;
for my $line (split /\R/, $source) {
$line =~ s/^([ ]*)//;
my $indent = length $1;
push @data, [$lineno++, $indent, $line];
}
此
我发现很难将其翻译成惯用的Python,因为re.sub()
仅在替换后返回字符串,而不是匹配对象(我需要计算删除的空格)。
在这个特定的示例中,我可以简单地比较替换前后字符串的长度。 但我对这类问题的一般解决方案感兴趣:
如何在访问正则表达式捕获的同时执行单个替换?
尝试1 - 通过替换函数来说明匹配对象:
lineno = 1
data = []
re_leading_space = re.compile(r'^([ ]*)')
for line in source.splitlines(): # TODO handle Unicode line seps
m = None
def exfiltrate(the_match):
nonlocal m
m = the_match
return ''
line = re_leading_space.sub(exfiltrate, line, count=1)
indent = len(m.group(1)) if m is not None else 0
data.append((lineno, indent, line))
lineno += 1
缺点:奇怪的nonlocal
数据流。
尝试2 - 手动执行替换:
lineno = 1
data = []
re_leading_space = re.compile(r'^([ ]*)')
for line in source.splitlines(): # TODO handle Unicode line seps
m = re_leading_space.match(line)
indent = 0
if m is not None:
line = line[m.end():] # remove matched prefix
indent = len(m.group(1))
data.append((lineno, indent, line))
lineno += 1
缺点:虽然相当清楚,但它最终会成为标准库的重新实现。
尝试3 - 执行匹配,然后再次匹配正则表达式作为替换:
lineno = 1
data = []
re_leading_space = re.compile(r'^([ ]*)')
for line in source.splitlines(): # TODO handle Unicode line seps
m = re_leading_space.match(line)
line = re_leading_space.sub('', line, count=1)
indent = len(m.group(1)) if m is not None else 0
data.append((lineno, indent, line))
lineno += 1
缺点:虽然比较简洁,但这种模式不必要地匹配两次。必须注意向match()
和sub()
提供相同的标记等。
那么这个问题的Pythonic解决方案是什么? 我找不到“一个也是唯一一个明显的方法来做到这一点。” 也许我错过了一个特定的习语?
答案 0 :(得分:2)
我强烈怀疑你会找到任何方法在Python中使用与Perl一样自然的正则表达式。正则表达式是Perl设计中非常低级别的一部分,而它们几乎不是Python的核心。
所以我的第一个建议是考虑你是否可以避免一起使用正则表达式。对于简单的示例问题,只需使用line.lstrip(' ')
并比较长度以确定删除了多少缩进。也许您考虑的其他一些问题也可以使用字符串方法轻松实现,而不是使用正则表达式。
我真的怀疑对于一般的正则表达式替换有任何解决方案,它比你考虑的所有选项都要好很多。我可能会使用类似你自己尝试2的东西,或者可能尝试使用内部函数保存缩进量,而不是匹配对象本身。
答案 1 :(得分:0)
Match objects有一个expand
方法,记录为:
返回通过执行反斜杠替换获得的字符串 模板字符串模板,由sub()方法完成。躲避如 \ n被转换为适当的字符和数字 反向引用(\ 1,\ 2)和命名反向引用(\ g< 1>,\ g)是 取而代之的是相应组的内容。
这允许仅匹配一次并使用匹配进行替换,如下所示:
data = []
re_leading_space = re.compile(r'^([ ]*)(.*)')
for lineno, line in enumerate(source.splitlines()): # TODO handle Unicode line seps
m = re_leading_space.match(line)
indent = 0
if m is not None:
line = m.expand(r'\2')
indent = len(m.group(1))
data.append((lineno, indent, line))