使用Python正则表达式解析字符串,使用命名组和替换。

4

我有一个特殊的用例,但我还不知道如何处理。我想根据字段名/字段长度拆分字符串。为此,我定义了一个正则表达式,如下所示:

'(?P<%s>.{%d})' % (field_name, field_length)

这个操作将重复应用于所有字段。

我还有一个正则表达式,可以删除每个字段右侧的空格:

self.re_remove_spaces = re.compile(' *$')

这样我就可以像这样获取每个字段:
def dissect(self, str):
    data = { }
    m = self.compiled.search(str)
    for field_name in self.fields:
        value = m.group_name(field_name)
        value = re.sub(self.re_remove_spaces, '', value)
        data[field_name] = value
    return data

我必须对数百万个字符串执行此处理,因此它必须高效。

令我困扰的是,我更喜欢使用compiled.sub而不是compiled.search一次性进行解析+空格去除,但我不知道如何做到这一点。

具体来说,我的问题是:

如何在Python正则表达式中结合命名组执行正则表达式替换?


你展示的正则表达式('(?<%s>.%d)' % ...,可能会变成类似'(?<name>.12)')不是有效的Python正则表达式。 - interjay
抱歉,已更正:缺少 P 和 {}。我仍在测试中,可能存在更多的错误。 - blueFast
2个回答

4

我理解每个字段都像在表格中一样,紧邻着排列在字符串中,例如:

name     description        license
python   language           opensource
windows  operating system   proprietry

假设您提前知道每个字段的长度,那么您可以更加简单地完成它,而无需使用正则表达式。(顺便说一下,str 不是一个好的变量名,因为它与内置的 str 类型冲突)
def dissect(text):
    data = {}
    for name, length in fields:
        data[name] = text[:length].rstrip()
        text = text[length:]
    return data

假设 fields = [('lang', 9), ('desc', 19), ('license', 12)]:

>>> self.dissect('python   language           opensource')
{'lang': 'python', 'license': 'opensource', 'desc': 'language'}

这是你试图做的吗?

是的,这正是我尝试做的事情,实际上我的第一次实现是使用字符串切片完成的。不知怎么的,我自己相信在这种情况下使用正则表达式会更快,于是我改变了实现方式,使用了带有命名组的正则表达式。我的进程函数现在运行了数小时,说实话我感觉切片要更快一些。也许我应该把它改回去,或者保留两种实现方式进行性能测试。 - blueFast
我觉得你会发现使用slice显著地更快,但如果它已经运行了这么长时间,那么你一定应该对它进行分析,并查看瓶颈在哪里以及如何进一步提高速度。 - aquavitae
由于需要处理的记录很多,所以花费了很长时间。 - blueFast
我对Python内部知之甚少,无法识别性能问题,但是让我问你一些事情:步骤text=text[length:]不是一个性能陷阱吗?我的意思是,你在为每个字段再次复制整个字符串,除了你刚刚提取的那部分。可能会有很多字段(我的格式目前有13个字段)。在我的原始实现中,我使用起始/结束位置来跟踪位置,并移动这些标记以提取我想要的每个字段的片段。我有一种感觉,这比复制字符串要快,但我没有数据支持。 - blueFast
你真的需要在上面运行一个分析器,找出性能问题所在,并且我怀疑复制字符串并不是其中之一——在Python中它非常快。如果这是你为每个记录调用的类方法,那么为每个记录调用函数的开销可能会是一个更大的问题。如果你发布正在执行此操作的完整代码,也许我们可以建议你在哪里可以提高性能。 - aquavitae
尽管这个解决方案没有使用正则表达式,但它是我最终使用的方案。 - blueFast

0

为什么要使用sub,当你可以直接匹配想要的部分呢?

你可以使用类似以下的代码:

(?P<name>.{0,N}(?<! ))

但是如果匹配必须完全是N个字符长,你可以使用前瞻,例如:

(?=(?P<name>.{0,N}(?<! ))).{N}

如果使用此方法比使用额外的修剪更有效仍有疑问。您可以尝试并告诉我们。

如果匹配项只是空格,同时其前面的字符也是空格,则这些表达式将无效。如果您需要使该情况起作用,可以在组的末尾添加|

(?P<name>.{0,N}(?<! )|)

是的,字段可以全部为空格,前面的字段也可以为空格。而且字段有一个精确的长度。 - blueFast
@gonvaled,你可以使用最后一条建议的第二个表达式:(?=(?P<name>.{0,N}(?<! )|)).{N} - Qtax
我无法修改输入数据。添加 | 不可行。 - blueFast
@gonvaled,什么数据?我在谈论正则表达式。按照写法使用适当的表达式。或者你是在问我们如何在不修改代码的情况下解决你的问题?. - Qtax
嗯,好的,抱歉,我误解了你的回答。那么 | 匹配的是什么? - blueFast
@gonvaled,|在正则表达式中表示“或”。 - Qtax

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接