Python как читать N строк одновременно - PullRequest
41 голосов
/ 14 июня 2011

Я пишу код для одновременной обработки огромного 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 строк, но мне чего-то не хватает

Ответы [ 6 ]

54 голосов
/ 14 июня 2011

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
6 голосов
/ 14 июня 2011

Вопрос, по-видимому, предполагает, что эффективность можно получить, читая «огромный текстовый файл» в блоках по N строк за раз. Это добавляет прикладной уровень буферизации к уже высоко оптимизированной библиотеке stdio, добавляет сложности и, вероятно, абсолютно ничего не покупает.

Таким образом:

with open('my_very_large_text_file') as f:
    for line in f:
        process(line)

, вероятно, превосходит любую альтернативу во времени, пространстве, сложности и удобочитаемости.

См. Также Первые два правила Роба Пайка , Два правила Джексона и PEP-20 Дзен Питона . Если вы действительно хотели поиграть с islice, вы должны были пропустить большой файл.

2 голосов
/ 14 июня 2011

Поскольку было добавлено требование, чтобы было статистически равномерное распределение линий, выбранных из файла, я предлагаю этот простой подход.

"""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 мс на вызов.

Естественный следующий вопрос: Случайное вообще едва ли случайно?* и ответ «субкриптографический, но, безусловно, подходит для биоинформатики».

0 голосов
/ 14 июня 2011

Предполагая, что «пакет» означает, что вы хотите обрабатывать все 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</p>

<h2>the final group</h2>

<p>process_list(interim_list)<br>
0 голосов
/ 14 июня 2011

Вот еще один способ использования 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 _, 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   
...
0 голосов
/ 14 июня 2011

Используется функция 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]"""
...