我正在编写一个代码,一次取一个巨大的文本文件(几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行,但我遗漏了一些东西
答案 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 rules,Jackson's Two Rules和PEP-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)