如何执行单个替换,然后使用正则表达式捕获?

时间:2018-02-19 20:47:11

标签: python regex

我正在将一个解析工具从Perl移植到Python:

my $lineno = 1;
my @data;
for my $line (split /\R/, $source) {
    $line =~ s/^([ ]*)//;
    my $indent = length $1;
    push @data, [$lineno++, $indent, $line];
}

  • 使用Unicode行分隔符
  • 将输入拆分为行
  • 剥离前导空格(仅限U + 0020空格字符),
  • 确定剥离空间的压痕级别。

我发现很难将其翻译成惯用的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解决方案是什么? 我找不到“一个也是唯一一个明显的方法来做到这一点。” 也许我错过了一个特定的习语?

2 个答案:

答案 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))