Как прочитать файл с переменными многострочными данными в Python

#python

#python

Вопрос:

У меня есть файл размером около 100 МБ, который выглядит следующим образом:

 #meta data 1    
skadjflaskdjfasljdfalskdjfl
sdkfjhasdlkgjhsdlkjghlaskdj
asdhfk
#meta data 2
jflaksdjflaksjdflkjasdlfjas
ldaksjflkdsajlkdfj
#meta data 3
alsdkjflasdjkfglalaskdjf
  

Этот файл содержит одну строку метаданных, которая соответствует нескольким данным переменной длины, содержащим только буквенно-цифровые символы. Каков наилучший способ считывания этих данных в простой список, подобный этому:

 data = [[#meta data 1, skadjflaskdjfasljdfalskdjflsdkfjhasdlkgjhsdlkjghlaskdjasdhfk],
       [#meta data 2, jflaksdjflaksjdflkjasdlfjasldaksjflkdsajlkdfj],
       [#meta data 3, alsdkjflasdjkfglalaskdjf]]
  

Моей первоначальной идеей было использовать read() метод для чтения всего файла в память, а затем использовать регулярные выражения для преобразования данных в желаемый формат. Есть ли лучший, более питонический способ? Все строки метаданных начинаются с восьмистишия, а все строки данных являются буквенно-цифровыми. Спасибо!

Ответ №1:

itertools.groupby предоставляет простой способ объединения строк в группы:

 import itertools

data=[]
with open('data.txt','r') as f:
    for key,group in itertools.groupby(f,lambda line: line.startswith('#meta')):
        if key:
            meta=next(group).strip()
        else:
            lines=''.join(group).strip()
            data.append((meta,lines))
print(data)            
  

выдает

 [('#meta data 1', 'skadjflaskdjfasljdfalskdjflnsdkfjhasdlkgjhsdlkjghlaskdjnasdhfk'), ('#meta data 2', 'jflaksdjflaksjdflkjasdlfjasnldaksjflkdsajlkdfj'), ('#meta data 3', 'alsdkjflasdjkfglalaskdjf')]
  

Выражение

 itertools.groupby(f,lambda line: line.startswith('#meta'))
  

возвращает итератор. Он перебирает строки в f и вызывает lambda функцию в каждой строке. Когда он встречает строку, начинающуюся с #meta , эта функция возвращает True , в противном случае False .

itertools.groupby собирает все смежные строки, которые возвращают одно и то же значение.

Итак, строка, начинающаяся с #meta , помещается в свою собственную группу, затем все последующие строки, не начинающиеся с #meta , помещаются в следующую группу и так далее.

key Это возвращаемое значение из lambda функции. В этом случае это будет либо True , либо False .

Комментарии:

1. Вау, это здорово! Единственное, с чем у меня возникают трудности, это то, что мой вывод выдает мне [(False, 'skadjflaskdjfasljdfalskdjflnsdkfjhasdlkgjhsdlkjghlaskdjnasdhfk')... Я, кажется, не понимаю, почему я получаю логическое значение и почему оно равно false?!

2. Похоже, что, возможно, вы печатаете key , а не meta ? Используете ли вы data.append((key,lines)) ? Если да, измените key —> meta .

Ответ №2:

Я не знаю, будет ли это самым быстрым способом, но из головы:

 data = []
with open('input.file', 'r') as fp:
    for line in fp:
        line = line.strip()
        if line[0] == '#':
            data.append((line, []))
        else:
            data[-1][1].append(line)
data = [(X, ''.join(Y)) for X, Y in data]
  

Комментарии:

1. Спасибо, это был классный ответ. Я никогда не думал делать это таким образом.

Ответ №3:

Я думаю, что-то вроде этого:

 result = []
for line in file.readlines():
    if line[0] == '#':
        result.append([line])
    else:
        if len(result[-1]) == 1:
            result[-1].append(line)
        else:
            result[-1][-1]  = line
  

Не тестировался.

Ответ №4:

Я бы сделал это просто, что-то вроде:

 data = [] # result
lastmeta = None # the last metadata line seen
chunks = [] # lines since the last metadata line
for line in input:
    if line[0] == '#': # metadata
        if lastmeta: # need to flush data we've collected
            data.append((lastmeta, ''.join(chunks))
        lastmeta = line
    else:
        chunks.append(line)