大量のテキストファイル(数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行の次のバッチを処理したいのですが、何かが足りないことです。
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
質問は、一度にN行のブロックで「巨大なテキストファイル」を読み取ることによって効率が得られると推測しているようです。これにより、既に高度に最適化されたstdio
ライブラリにバッファリングのアプリケーション層が追加され、複雑さが増し、おそらくまったく何も買わないでしょう。
したがって:
with open('my_very_large_text_file') as f:
for line in f:
process(line)
時間、スペース、複雑さ、読みやすさにおいて、おそらく他の選択肢よりも優れています。
Rob Pikeの最初の2つのルール 、 Jacksonの2つのルール 、および PEP-20 The Python of Zen も参照してください。あなたが本当にislice
で遊びたいだけなら、あなたは大きなファイルのものを省くべきでした。
ファイルから選択された行の統計的に均一な分布があるという要件が追加されたため、この単純なアプローチを提供します。
"""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)
ディスク上の1.7GBを含む300万行の模擬データファイルでテストしました。それほど暑くないデスクトップでは、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で10,000サンプルまで線形にスケーリングされました。
自然な次の質問は ランダムはほとんどランダムですか? であり、答えは「サブ暗号ですが、バイオインフォマティクスには確かに問題ありません」です。
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
...
_
「バッチ」とは、16個のレコードすべてを個別に処理するのではなく一度に処理し、ファイルを一度に1レコードずつ読み取り、カウンターを更新することを意味します。カウンターが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)
チャンク内のリストを反復処理する最も「Python」の方法は何ですか? :
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]"""