Python如何一次读取N行

时间:2011-06-13 20:20:42

标签: python lines itertools

我正在编写一个代码,一次取一个巨大的文本文件(几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行,但我遗漏了一些东西

6 个答案:

答案 0 :(得分:54)

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

另一种方法是使用grouper pattern

with open(...) as f:
    for next_n_lines in izip_longest(*[f] * n):
        # process next_n_lines

答案 1 :(得分:6)

这个问题似乎假定通过一次读取N行中的“巨大文本文件”可以获得效率。这为已经高度优化的stdio库添加了一个缓冲应用层,增加了复杂性,并且可能绝对没有给你买任何东西。

因此:

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

在时间,空间,复杂性和可读性方面可能优于任何替代方案。

另请参阅Rob Pike's first two rulesJackson's Two RulesPEP-20 The Zen of Python。如果你真的只想玩islice,你应该省去大文件。

答案 2 :(得分:2)

由于要求添加了从文件中选择的行的统计分布,我提供了这种简单的方法。

"""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)

我在300万行的模拟数据文件上测试了它,其中包含1.7GB的磁盘。 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μs内完成,并在每次通话47ms时线性扩展至10k个样本。

自然的下一个问题是Random is barely random at all?,答案是“亚密码,但对于生物信息学来说肯定很好”。

答案 3 :(得分:0)

来自What is the most “pythonic” way to iterate over a list in chunks?的使用过的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]"""

答案 4 :(得分:0)

以下是使用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函数lambda _, c=count(): c.next()/N,并使用c参数绑定到{{{ 3}}当count()所以每次groupby()都会调用lambda函数并评估返回值以确定将对行进行分组的分组器:

# 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   
...

答案 5 :(得分:0)

假设“批处理”意味着要一次处理所有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)