Python, как читать N количество строк за раз
Я пишу код для получения огромного текстового файла (несколько GB) N строк за раз, обрабатывая пакет и переходите на следующие N строк, пока я не закончу весь файл. (Мне все равно, если последняя партия не идеальный размер).
Я читал об использовании itertools islice для этой операции. Я думаю, что я на полпути:
from itertools import islice
N = 16
infile = open("my_very_large_text_file", "r")
lines_gen = islice(infile, N)
for lines in lines_gen:
...process my lines...
Проблема в том, что я хотел бы обработать следующую партию из 16 строк, но мне что-то не хватает
Ответы
Ответ 1
islice()
может использоваться для получения следующих n
элементов итератора. Таким образом, list(islice(f, n))
вернет список из следующих n
строк файла f
. Использование этого в цикле даст вам файл в виде кусков из n
строк. В конце файла список может быть короче, и, наконец, вызов вернет пустой список.
from itertools import islice
with open(...) as f:
while True:
next_n_lines = list(islice(f, n))
if not next_n_lines:
break
# process next_n_lines
Альтернативой является использование шаблона группировщика:
with open(...) as f:
for next_n_lines in izip_longest(*[f] * n):
# process next_n_lines
Ответ 2
Вопрос, по-видимому, предполагает, что эффективность можно получить, читая "огромный текстовый файл" в блоках по N строк одновременно. Это добавляет прикладной уровень буферизации к уже высоко оптимизированной библиотеке stdio
, добавляет сложности и, вероятно, абсолютно ничего не покупает.
Таким образом:
with open('my_very_large_text_file') as f:
for line in f:
process(line)
вероятно, превосходит любую альтернативу во времени, пространстве, сложности и удобочитаемости.
См. Также первые два правила Роба Пайка, два правила Джексона и PEP-20 "Дзен Питона". Если вы действительно хотите поиграть с islice
вы должны были пропустить большие файлы.
Ответ 3
Поскольку требование было добавлено, что существует статистически однородное распределение строк, выбранных из файла, я предлагаю этот простой подход.
"""randsamp - extract a random subset of n lines from a large file"""
import random
def scan_linepos(path):
"""return a list of seek offsets of the beginning of each line"""
linepos = []
offset = 0
with open(path) as inf:
# WARNING: CPython 2.7 file.tell() is not accurate on file.next()
for line in inf:
linepos.append(offset)
offset += len(line)
return linepos
def sample_lines(path, linepos, nsamp):
"""return nsamp lines from path where line offsets are in linepos"""
offsets = random.sample(linepos, nsamp)
offsets.sort() # this may make file reads more efficient
lines = []
with open(path) as inf:
for offset in offsets:
inf.seek(offset)
lines.append(inf.readline())
return lines
dataset = 'big_data.txt'
nsamp = 5
linepos = scan_linepos(dataset) # the scan only need be done once
lines = sample_lines(dataset, linepos, nsamp)
print 'selecting %d lines from a file of %d' % (nsamp, len(linepos))
print ''.join(lines)
Я протестировал его в файле фальшивых данных из 3 миллионов строк, содержащих 1,7 ГБ на диске. scan_linepos
доминировал над временем выполнения, занимая около 20 секунд на моем не очень горячем рабочем столе.
Просто, чтобы проверить производительность sample_lines
, я использовал модуль timeit
как таковой
import timeit
t = timeit.Timer('sample_lines(dataset, linepos, nsamp)',
'from __main__ import sample_lines, dataset, linepos, nsamp')
trials = 10 ** 4
elapsed = t.timeit(number=trials)
print u'%dk trials in %.2f seconds, %.2fµs per trial' % (trials/1000,
elapsed, (elapsed/trials) * (10 ** 6))
При различных значениях nsamp
; когда nsamp
равнялось 100, один sample_lines
завершался в 460 мкс и масштабировался линейно до 10 тыс. выборок при 47 мс за вызов.
Естественный следующий вопрос Случайный вообще не случайен вообще?, и ответ "субкриптографический, но, безусловно, отлично подходит для биоинформатики".
Ответ 4
Используется функция chunker из Каков самый "питонический" способ перебора списка в кусках?:
from itertools import izip_longest
def grouper(iterable, n, fillvalue=None):
"grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
args = [iter(iterable)] * n
return izip_longest(*args, fillvalue=fillvalue)
with open(filename) as f:
for lines in grouper(f, chunk_size, ""): #for every chunk_sized chunk
"""process lines like
lines[0], lines[1] , ... , lines[chunk_size-1]"""
Ответ 5
Вот еще один способ: groupby:
from itertools import count, groupby
N = 16
with open('test') as f:
for g, group in groupby(f, key=lambda _, c=count(): c.next()/N):
print list(group)
Как это работает:
В основном groupby() будет группировать строки по возвращаемому значению ключевого параметра, а ключевым параметром является lambda function lambda _, c=count(): c.next()/N
и используя тот факт, что аргумент c будет привязан к count(), когда функция будет определяться, чтобы каждый раз groupby()
вызывал лямбда-функцию и оценивал возвращаемое значение для определения группы, которая будет группировать строки так:
# 1 iteration.
c.next() => 0
0 / 16 => 0
# 2 iteration.
c.next() => 1
1 / 16 => 0
...
# Start of the second grouper.
c.next() => 16
16/16 => 1
...
Ответ 6
Предполагая, что "пакет" означает захотеть обработать все 16 рецентов за один раз, а не индивидуально, сначала прочитать файл по одной записи и обновить счетчик; когда счетчик достигает 16, обработайте эту группу.
interim_list = []
infile = open("my_very_large_text_file", "r")
ctr = 0
for rec in infile:
interim_list.append(rec)
ctr += 1
if ctr > 15:
process_list(interim_list)
interim_list = []
ctr = 0
the final group
process_list(interim_list)