【问题标题】:How to split file into chunks by string delimiter in Python如何在 Python 中通过字符串分隔符将文件拆分为块
【发布时间】:2016-06-16 20:42:48
【问题描述】:

我需要将一个可能很大的 csv 文件上传到我的应用程序中。该文件的每个部分都由#TYPE * 指示。我应该如何将其拆分为块并对每个块进行进一步处理?每个块都是一个标题列表,后面跟着所有的值。

现在我已经编写了单个块的处理,但我不确定如何对每个块进行操作。我认为正则表达式操作将是最佳选择,因为#TYPE * 会不断返回。

#TYPE Lorem.Text.A
...
#TYPE Lorem.Text.B
...
#TYPE Lorem.Text.C
...

更新

此解决方案已从将所有部分保存在一个文件中更改为将所有部分保存到单独的文件中并将它们压缩到一个 zip 文件中。这个 zip 文件由 python 读取并进一步分析。如果有人对此解释感兴趣,请给我留言,我会更新这个问题。

@Padraic 的回答对旧课程最有帮助。

【问题讨论】:

    标签: python regex itertools


    【解决方案1】:

    您可以使用 groupby 假设这些部分由以 #TYPE 开头的行分隔:

    from itertools import groupby, chain
    
    
    def get_sections(fle):
        with open(fle) as f:
            grps = groupby(f, key=lambda x: x.lstrip().startswith("#TYPE"))
            for k, v in grps:
                if k:
                    yield chain([next(v)], (next(grps)[1]))  # all lines up to next #TYPE
    

    您可以在迭代时获取每个部分:

    In [13]: cat in.txt
    #TYPE Lorem.Text.A
    first
    #TYPE Lorem.Text.B
    second
    #TYPE Lorem.Text.C
    third
    
    In [14]: for sec in get_sections("in.txt"):
       ....:     print(list(sec))
       ....:     
    ['#TYPE Lorem.Text.A\n', 'first\n']
    ['#TYPE Lorem.Text.B\n', 'second\n']
    ['#TYPE Lorem.Text.C\n', 'third\n']
    

    如果没有其他行以# 开头,那么仅此一项就足以在startswith 中使用,您的模式没有什么复杂的,因此它不是正则表达式的真正用例。这也一次只将一个部分而不是整个文件存储到内存中。

    如果您没有前导空格,并且 # 出现的唯一位置是在 TYPE 之前,则只需调用 groupby 就足够了:

    from itertools import groupby, chain
    
    
    def get_sections(fle):
        with open(fle) as f:
            grps = groupby(f)
            for k, v in grps:
                if k:
                    yield chain([next(v)], (next(grps)[1]))  # all lines up to next #TYPE
    

    如果一开始有一些元数据,您可以使用 dropwhile 来跳过行,直到我们点击 #Type 然后只是分组:

    from itertools import groupby, chain, dropwhile
    
    
    def get_sections(fle):
        with open(fle) as f:
            grps = groupby(dropwhile(lambda x: not x.startswith("#"), f))
            for k, v in grps:
                if k:
                    yield chain([next(v)], (next(grps)[1]))  # all lines up to next #TYPE
    

    演示:

    In [16]: cat in.txt
    meta
    more meta
    #TYPE Lorem.Text.A
    first
    #TYPE Lorem.Text.B
    second
    second
    #TYPE Lorem.Text.C
    third
    
    In [17]: for sec in get_sections("in.txt"):
                print(list(sec))
       ....:     
    ['#TYPE Lorem.Text.A\n', 'first\n']
    ['#TYPE Lorem.Text.B\n', 'second\n', 'second\n']
    ['#TYPE Lorem.Text.C\n', 'third\n']
    

    【讨论】:

    • 我会看看你的建议。
    • @AdrianZ.. 它肯定会起作用,并且避免在内存中存储数据的多个副本,如果您使用 .read 和 re.split 您将在内存中存储两个完整的数据副本,如果您的文件很大可能不可能
    • 它看起来非常好,我喜欢不浪费内存的想法。现在我没有从打印中得到任何输出,但我会继续测试一段时间并返回我的结果。
    • @AdrianZ。自从你第一次发表评论以来,我编辑了代码,所以请确保你使用的是最新的。
    • 出于某种原因,我不能使用 @ 作为你的名字。我可能做错了什么,但两个例子似乎都在做不同的事情。带有 groupby(f) 的示例将每一行分隔开,带有 lamba 的 groupby 似乎遗漏了第二部分/块。
    【解决方案2】:

    根据#TYPE之前存在的新行char进行拆分

    chunks = re.split(r'\n(?=#TYPE\b *)', f.read())
    

    例子:

    >>> import re
    >>> s = '''#TYPE Lorem.Text.A
    ...
    #TYPE Lorem.Text.B
    ...
    #TYPE Lorem.Text.C
    ...'''
    >>> re.split(r'\n(?=#TYPE *)', s)
    ['#TYPE Lorem.Text.A\n...', '#TYPE Lorem.Text.B\n...', '#TYPE Lorem.Text.C\n...']
    >>> 
    

    【讨论】:

    • 您不应该以^ 而不是\n 开头吗?我相信如果第一行匹配,您的解决方案将无法正常工作。
    • @zondo 您是否希望将第一行拆分为 ['', '#TYPE'] ?而且也没有必要使用^
    • 在我的测试用例中看起来不错。只是有点乱,但没关系。我还要测试另一个答案。我很好奇哪一个对性能的影响最小。
    猜你喜欢
    • 2011-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-13
    • 2013-03-03
    • 1970-01-01
    • 2018-05-05
    相关资源
    最近更新 更多