大きなファイルを読み取る方法-行ごとに?


536

ファイル全体の各行を反復処理したい。これを行う1つの方法は、ファイル全体を読み取り、それをリストに保存してから、対象の行に移動することです。この方法は大量のメモリを使用するため、別の方法を探しています。

これまでの私のコード:

for each_line in fileinput.input(input_file):
    do_something(each_line)

    for each_line_again in fileinput.input(input_file):
        do_something(each_line_again)

このコードを実行すると、エラーメッセージが表示されますdevice active

助言がありますか?

目的はペアワイズ文字列の類似性、つまりファイル内の各行の意味を計算することです。他のすべての行とのレーベンシュタイン距離を計算します。


4
行ごとにファイル全体を再度読み取る必要があるのはなぜですか?誰かが達成しようとしていることを誰かが言ったとしたら、もっと良いアプローチを提案できるかもしれません。
JJJ

回答:


1269

ファイルを読み取るための正しい完全なPythonの方法は次のとおりです。

with open(...) as f:
    for line in f:
        # Do something with 'line'

このwithステートメントは、内部ブロックで例外が発生した場合も含め、ファイルのオープンとクローズを処理します。for line in fはファイルオブジェクトfを反復可能オブジェクトとして扱います。これは、バッファリングされたI / Oとメモリ管理を自動的に使用するため、大きなファイルを心配する必要はありません。

それを行うには明白な方法が1つ(できれば1つだけ)あるはずです。


14
うん、これはpython 2.6以上で最高のバージョンです
Simon Bergot

3
私は個人的には、データパイプラインを処理するためのジェネレーターとコルーチンを好みます。
jldupont

4
ファイルが巨大なテキストファイルであるが、1行でアイデアが単語を処理する場合の最善の戦略は何ですか?
mfcabrera 2013

4
誰かがどのようfor line in f:に働いているか説明できますか?つまり、ファイルオブジェクトの反復処理はどのように可能ですか?
2015

11
オブジェクトを反復処理する場合、Pythonはオブジェクトメソッドのリストでと呼ばれる特別なメソッドを検索し、__iter__何をすべきかを指示します。ファイルオブジェクトは、この特別なメソッドを定義して、行をまたぐイテレータを返します。(大体。)
カトリエル

130

ランク付けされた2つのメモリ効率的な方法(最初が最良)-

  1. の使用with-Python 2.5以降でサポート
  2. yieldどれだけ読むかをコントロールしたい場合に使用

1.の使用 with

with大きなファイルを読み取るための優れた効率的なpythonicの方法です。利点-1)with実行ブロックを終了した後、ファイルオブジェクトは自動的に閉じられます。2)withブロック内の例外処理。3)メモリforループは、fファイルオブジェクトを1行ずつ反復します。内部的には、バッファされたIO(コストのかかるIO操作を最適化するため)とメモリ管理を行います。

with open("x.txt") as f:
    for line in f:
        do something with data

2.の使用 yield

場合によっては、各反復で読み取る量をより細かく制御したい場合があります。その場合はiteryieldを使用してください。この方法では、最後にファイルを明示的に閉じる必要があることに注意してください。

def readInChunks(fileObj, chunkSize=2048):
    """
    Lazy function to read a file piece by piece.
    Default chunk size: 2kB.
    """
    while True:
        data = fileObj.read(chunkSize)
        if not data:
            break
        yield data

f = open('bigFile')
for chuck in readInChunks(f):
    do_something(chunk)
f.close()

落とし穴と完全を期すために -以下の方法は、大きなファイルを読み取るのに適していないか、エレガントではありませんが、丸く理解するために読んでください。

Pythonでは、ファイルから行を読み取る最も一般的な方法は、次のことです。

for line in open('myfile','r').readlines():
    do_something(line)

ただし、これが行われると、readlines()関数(関数に同じread())がファイル全体をメモリにロードし、それを繰り返し処理します。大きなファイルの場合はfileinput、次のようにモジュールを使用することで、少し優れたアプローチ(最初に説明した2つの方法が最適です)を使用できます。

import fileinput

for line in fileinput.input(['myfile']):
    do_something(line)

fileinput.input()コールは、順次ラインを読み、彼らが読み込まれ、あるいは単にので、このした後から、メモリに保管していないfilepythonで反復可能です。

参考文献

  1. ステートメント付きのPython

9
-1基本的に、これを行うのは決して良いことではありませんfor line in open(...).readlines(): <do stuff>。なぜあなたは?Pythonの巧妙なバッファリングされたイテレータIOのすべての利点を失っただけで、利益はありません。
Katriel

5
@Srikar:問題に対して考えられるすべての解決策を提供するための時間と場所があります。初心者にファイル入力の方法を教えることはそれではありません。間違った答えでいっぱいの長い投稿の下部に正しい答えを埋め込むのは、良い教え方ではありません。
Katriel

6
@Srikar:正しい方法を上部に配置し、readlines(ファイルをメモリに読み込むため)実行するのが好ましくない理由を述べて説明し、fileinputモジュールの機能と理由を説明することで、投稿を大幅に改善できます。他の方法でそれを使用したい場合は、ファイルをチャンクすることでIOがどのように改善されるかを説明し、チャンク機能の例を示します(ただし、Pythonがこれをすでに行っているので、必要はありません)。しかし、単純な問題を解決するために5つの方法を提供するだけでは、この場合4つは間違っていますが、良い方法ではありません。
Katriel

2
完全を期すために追加するものは、最初ではなく最後に追加してください。最初に適切な方法を示します。
m000

6
@katrielalexは私の答えを再検討し、それが再編を正当化することを発見しました。以前の答えがどのように混乱を引き起こす可能性があるのか​​がわかります。うまくいけば、これは将来のユーザーにとって明確になるでしょう。
Srikar Appalaraju 2013

37

改行を取り除くには:

with open(file_path, 'rU') as f:
    for line_terminated in f:
        line = line_terminated.rstrip('\n')
        ...

ユニバーサル改行のサポートのすべてのテキストファイルの行がで終了しているように見えるだろう'\n'どんなファイルでターミネーター、、 、'\r''\n'または'\r\n'

編集-ユニバーサル改行サポートを指定するには:

  • Unix上のPython 2--open(file_path, mode='rU')必須[ @Daveに感謝]
  • Windows上のPython 2-- open(file_path, mode='rU')オプション
  • Python 3-- open(file_path, newline=None)オプション

newlineパラメータだけにPythonの3、デフォルトでサポートされていますNonemodeパラメータのデフォルトは'r'、すべての場合インチ U他のいくつかのメカニズムが翻訳し表示され、Windows上のPython 3でのPython 2で推奨されていません\r\n\n

ドキュメント:

ネイティブ行ターミネーターを保持するには:

with open(file_path, 'rb') as f:
    with line_native_terminated in f:
        ...

バイナリモードでも、を使用してファイルを行に解析できますin。各行には、ファイル内のターミネーターが含まれています。

おかげ@katrielalex答えは、Pythonのオープン()ドキュメント、およびiPythonの実験。


1
Python 2.7ではopen(file_path, 'rU')、ユニバーサル改行を有効にする必要がありました。
Dave

17

これはPythonでファイルを読み取る可能な方法です:

f = open(input_file)
for line in f:
    do_stuff(line)
f.close()

完全なリストは割り当てられません。それは行を繰り返します。


2
これは機能しますが、標準的な方法ではありません。正規の方法は、のようなコンテキストラッパーを使用することwith open(input_file) as f:です。これはあなたを救い、f.close()誤ってそれを閉じることを忘れないようにします。ファイルの読み取り時に非常に重要なメモリリークなどを防止します。
2016

1
@Mastが言ったように、それは標準的な方法ではないので、そのために反対票を投じます。
azuax 2017年

12

私がどこから来たのかについて前もっていくつかのコンテキスト。コードスニペットは最後にあります。

可能な場合は、H2Oなどのオープンソースツールを使用して超高性能の並列CSVファイルを読み取ることを好みますが、このツールは機能セットに制限があります。監視付き学習のためにH2Oクラスターにフィードする前に、データサイエンスパイプラインを作成するために多くのコードを作成することになります。

マルチプロセッシングライブラリのプールオブジェクトとマップ関数に多数の並列処理を追加することで、UCIリポジトリから8GB HIGGSデータセットや40GB CSVファイルなどのデータサイエンスファイルを非常に高速に読み込んでいます。たとえば、最近傍検索を使用したクラスタリングと、DBSCANおよびマルコフのクラスタリングアルゴリズムでは、深刻な問題のあるメモリと実時間の問題を回避するために、並列プログラミングの巧妙さが必要です。

私は通常、最初にgnuツールを使用してファイルを行ごとの部分に分割し、次にそれらすべてをglob-filemaskして、Pythonプログラムでそれらを並列に検索して読み取ります。私は1000以上の部分ファイルをよく使用しています。これらのトリックを実行すると、処理速度とメモリ制限が非常に役立ちます。

パンダdataframe.read_csvはシングルスレッドなので、これらのトリックを実行して、並列実行のためにmap()を実行することにより、パンダを非常に高速にすることができます。htopを使用して、単純な古いシーケンシャルパンダdataframe.read_csvを使用すると、1つのコアの100%cpuが、ディスクではなくpd.read_csvの実際のボトルネックであることを確認できます。

高速ビデオカードバスでSSDを使用しており、SATA6バスで回転するHDではなく、16 CPUコアを使用していることを追加する必要があります。

また、一部のアプリケーションでうまく機能することを発見した別の手法は、1つの大きなファイルを多数のパーツファイルに事前分割するのではなく、1つの巨大なファイル内のすべてを並列CSVファイルで読み取り、各ワーカーをファイル内の異なるオフセットで開始することです。各並列ワーカーでpythonのファイルseek()およびtell()を使用して、大きなテキストファイルをストリップで、大きなファイルの異なるバイトオフセットの開始バイトと終了バイトの場所で、同時にすべて読み取ります。バイトに対して正規表現findallを実行し、改行の数を返すことができます。これは部分的な合計です。最後に部分的な合計を合計して、ワーカーが終了した後にmap関数が戻ったときにグローバルな合計を取得します。

以下は、並列バイトオフセットトリックを使用したベンチマークの例です。

私は2つのファイルを使用しています:HIGGS.csvは8 GBです。これは、UCI機械学習リポジトリからのものです。all_bin .csvは40.4 GBで、現在のプロジェクトからのものです。私は2つのプログラムを使用します。Linuxに付属するGNU wcプログラムと、私が開発した純粋なpython fastread.pyプログラムです。

HP-Z820:/mnt/fastssd/fast_file_reader$ ls -l /mnt/fastssd/nzv/HIGGS.csv
-rw-rw-r-- 1 8035497980 Jan 24 16:00 /mnt/fastssd/nzv/HIGGS.csv

HP-Z820:/mnt/fastssd$ ls -l all_bin.csv
-rw-rw-r-- 1 40412077758 Feb  2 09:00 all_bin.csv

ga@ga-HP-Z820:/mnt/fastssd$ time python fastread.py --fileName="all_bin.csv" --numProcesses=32 --balanceFactor=2
2367496

real    0m8.920s
user    1m30.056s
sys 2m38.744s

In [1]: 40412077758. / 8.92
Out[1]: 4530501990.807175

これは、4.5 GB /秒、つまり45 Gb /秒のファイルスラッピング速度です。回転しているハードディスクではありません、私の友人。これは実際にはSamsung Pro 950 SSDです。

以下は、純粋なCコンパイルプログラムであるgnu wcによって行カウントされる同じファイルの速度ベンチマークです。

この場合、私の純粋なpythonプログラムが、gnu wcでコンパイルされたCプログラムの速度と基本的に一致していることがわかります。Pythonは解釈されますが、Cはコンパイルされるので、これはかなり興味深い速度の偉業です。もちろん、wcは実際には並列プログラムに変更する必要があります。そうすれば、私のpythonプログラムの靴下を打ち負かすことになります。しかし、今日のように、gnu wcは単なるシーケンシャルプログラムです。あなたはあなたができることをします、そしてpythonは今日並行して行うことができます。Cythonのコンパイルが私を助けることができるかもしれません(しばらくの間)。また、メモリマップファイルはまだ調査されていません。

HP-Z820:/mnt/fastssd$ time wc -l all_bin.csv
2367496 all_bin.csv

real    0m8.807s
user    0m1.168s
sys 0m7.636s


HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=16 --balanceFactor=2
11000000

real    0m2.257s
user    0m12.088s
sys 0m20.512s

HP-Z820:/mnt/fastssd/fast_file_reader$ time wc -l HIGGS.csv
11000000 HIGGS.csv

real    0m1.820s
user    0m0.364s
sys 0m1.456s

結論:速度は、Cプログラムと比較して、純粋なPythonプログラムの方が優れています。ただし、少なくとも行カウントの目的では、Cプログラムよりも純粋なpythonプログラムを使用するだけでは不十分です。通常、この手法は他のファイル処理にも使用できるため、このpythonコードは依然として優れています。

質問:正規表現を1回だけコンパイルしてすべてのワーカーに渡すと、速度が向上しますか?回答:このアプリケーションでは、正規表現のプリコンパイルは役に立ちません。その理由は、すべてのワーカーのプロセスのシリアル化と作成のオーバーヘッドが支配的であるからだと思います。

もう一つ。並列CSVファイルの読み取りは役に立ちますか?ディスクはボトルネックですか、それともCPUですか。Stackoverflowに関するいわゆるトップレートの回答の多くには、ファイルを読み取るのに必要なスレッドは1つだけであるという一般的な開発の知恵が含まれています。彼らは確かですか?

確認してみましょう:

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=16 --balanceFactor=2
11000000

real    0m2.256s
user    0m10.696s
sys 0m19.952s

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=1 --balanceFactor=1
11000000

real    0m17.380s
user    0m11.124s
sys 0m6.272s

そうそうそう。並列ファイル読み取りは非常にうまく機能します。さてさようなら!

PS 知りたい人がいる場合、単一のワーカープロセスを使用しているときにbalanceFactorが2だったらどうなるでしょうか。まあ、それは恐ろしいです:

HP-Z820:/mnt/fastssd/fast_file_reader$ time python fastread.py --fileName="HIGGS.csv" --numProcesses=1 --balanceFactor=2
11000000

real    1m37.077s
user    0m12.432s
sys 1m24.700s

fastread.py Pythonプログラムの主要部分:

fileBytes = stat(fileName).st_size  # Read quickly from OS how many bytes are in a text file
startByte, endByte = PartitionDataToWorkers(workers=numProcesses, items=fileBytes, balanceFactor=balanceFactor)
p = Pool(numProcesses)
partialSum = p.starmap(ReadFileSegment, zip(startByte, endByte, repeat(fileName))) # startByte is already a list. fileName is made into a same-length list of duplicates values.
globalSum = sum(partialSum)
print(globalSum)


def ReadFileSegment(startByte, endByte, fileName, searchChar='\n'):  # counts number of searchChar appearing in the byte range
    with open(fileName, 'r') as f:
        f.seek(startByte-1)  # seek is initially at byte 0 and then moves forward the specified amount, so seek(5) points at the 6th byte.
        bytes = f.read(endByte - startByte + 1)
        cnt = len(re.findall(searchChar, bytes)) # findall with implicit compiling runs just as fast here as re.compile once + re.finditer many times.
    return cnt

PartitionDataToWorkersのdefは、通常の順次コードです。他の誰かが並列プログラミングとはどのようなものかについて練習したい場合に備えて、私はそれを省略しました。学習のために、より難しい部分、つまりテスト済みで動作する並列コードを無料で提供しました。

おかげで:ArnoとCliffによるオープンソースのH2Oプロジェクトと、H2Oスタッフによる優れたソフトウェアと教育用ビデオは、上に示したこの純粋なpython高性能パラレルバイトオフセットリーダーにインスピレーションを与えてくれました。H2Oは、Javaを使用して並列ファイルの読み取りを行い、PythonとRプログラムから呼び出し可能で、巨大なCSVファイルの読み取りにおいて、地球上で最も高速です。


並列チャンクは基本的にこれです。また、SSDとフラッシュが、この手法で互換性のある唯一のストレージデバイスであると思います。スピニングHDは互換性がない可能性があります。
ジェフリーアンダーソン

1
OSキャッシングディスクファイルをどのように説明しましたか?
JamesThomasMoon1979

5

Katrielalexは、1つのファイルを開いて読み取る方法を提供しました。

ただし、アルゴリズムの方法では、ファイルの各行のファイル全体を読み取ります。つまり、Nがファイル内の行数である場合、ファイルの読み取りとレーベンシュタイン距離の計算の全体的な量はN * Nになります。あなたはファイルサイズを心配していて、それをメモリに保持したくないので、結果として生じる2次ランタイムが心配です。あなたのアルゴリズムは、O(n ^ 2)クラスのアルゴリズムにあります。これは、多くの場合、特殊化することで改善できます。

ここではすでにメモリとランタイムのトレードオフを知っていると思いますが、複数のレーベンシュタイン距離を並列で計算する効率的な方法があるかどうかを調査したいと思うかもしれません。もしそうなら、ここであなたの解決策を共有することは興味深いでしょう。

ファイルの行数、アルゴリズムを実行するマシンの種類(メモリとCPUパワー)、および許容ランタイムは何ですか?

コードは次のようになります。

with f_outer as open(input_file, 'r'):
    for line_outer in f_outer:
        with f_inner as open(input_file, 'r'):
            for line_inner in f_inner:
                compute_distance(line_outer, line_inner)

しかし、問題はどのように距離(行列?)を格納するかであり、たとえば、outer_lineを処理するために準備したり、再利用のためにいくつかの中間結果をキャッシュしたりする利点を得ることができます。


私のポイントは、この投稿には質問への回答が含まれておらず、さらにいくつかの質問が含まれていることです!IMOコメントとしてより適しています。
Katriel

1
@katriealex:エラー。奇妙な。ネストされたループを見て、実際の質問に合わせて独自の答えを拡張しましたか?ここで私の質問から私の質問を削除することができます。これを(部分的ではありますが)回答として提供することを保証する十分なコンテンツがまだあります。独自の回答を編集して、ネストされたループの例(質問によって明示的に質問されたもの)を含めるようにした場合も受け入れることができます。そうすれば、自分の回答を喜んで削除できます。しかし、反対票は私にはまったく得られないものです。
cfi

けっこうだ; ネストされたforループのデモを質問への答えとして実際に示すことはあまりありませんが、初心者をかなりターゲットにしていると思います。反対投票が削除されました。
Katriel

3
#Using a text file for the example
with open("yourFile.txt","r") as f:
    text = f.readlines()
for line in text:
    print line
  • 読み取り用にファイルを開きます(r)
  • ファイル全体を読み取り、各行をリスト(テキスト)に保存します
  • 各行を出力するリストをループします。

たとえば、特定の行を10を超える長さでチェックする場合は、すでに利用可能なものを使用してください。

for line in text:
    if len(line) > 10:
        print line

1
この質問には最適ではありませんが、このコードは主に、探しているものが "丸呑み"(ファイル全体を一度に読み取る)の場合に役立ちます。それは私のケースであり、グーグルは私をここに連れて行きました。+1。また、原子性のため、またはループで時間のかかる処理を行うと、ファイル全体を読み取るのが速くなる可能性があります
ntg

1
また、コードを少し改善しました:1. withの後にcloseは必要ありません:(docs.python.org/2/tutorial/inputoutput.html、「withキーワードを使用するのは良い習慣です...」を検索してください)2 。テキストは、ファイルが読み取られた後に処理できます(with loop ...などの)
ntg

2

fileinput .input()のpythonドキュメントから:

これはsys.argv[1:]、にリストされているすべてのファイルの行を反復処理し、デフォルトでsys.stdinリストが空の場合に実行されます

さらに、関数の定義は次のとおりです。

fileinput.FileInput([files[, inplace[, backup[, mode[, openhook]]]]])

行の間を読んでいると、これはfilesリストになる可能性があるので、次のようになります。

for each_line in fileinput.input([input_file, input_file]):
  do_something(each_line)

詳細はこちらをご覧ください


2

ひどく遅いので、デフォルトのファイルのロードを使用しないことを強くお勧めします。numpy関数とIOpro関数(たとえば、numpy.loadtxt())を調べる必要があります。

http://docs.scipy.org/doc/numpy/user/basics.io.genfromtxt.html

https://store.continuum.io/cshop/iopro/

次に、ペアワイズ操作をチャンクに分割できます。

import numpy as np
import math

lines_total = n    
similarity = np.zeros(n,n)
lines_per_chunk = m
n_chunks = math.ceil(float(n)/m)
for i in xrange(n_chunks):
    for j in xrange(n_chunks):
        chunk_i = (function of your choice to read lines i*lines_per_chunk to (i+1)*lines_per_chunk)
        chunk_j = (function of your choice to read lines j*lines_per_chunk to (j+1)*lines_per_chunk)
        similarity[i*lines_per_chunk:(i+1)*lines_per_chunk,
                   j*lines_per_chunk:(j+1)*lines_per_chunk] = fast_operation(chunk_i, chunk_j) 

要素ごとに行うよりも、データをチャンクにロードしてから行列演算を行う方が、ほとんどの場合ずっと高速です。


0

最後の位置の読み取りから大きなファイルを頻繁に読み取る必要がありますか?

Apache access.logファイルを1日に数回カットするために使用するスクリプトを作成しました。したがって、最後の実行中に解析された最後の行に位置カーソル設定する必要がありました。この目的のために、私が使用されるfile.seek()file.seek()、ファイル内のカーソルの記憶を可能にする方法。

私のコード:

ENCODING = "utf8"
CURRENT_FILE_DIR = os.path.dirname(os.path.abspath(__file__))

# This file is used to store the last cursor position
cursor_position = os.path.join(CURRENT_FILE_DIR, "access_cursor_position.log")

# Log file with new lines
log_file_to_cut = os.path.join(CURRENT_FILE_DIR, "access.log")
cut_file = os.path.join(CURRENT_FILE_DIR, "cut_access", "cut.log")

# Set in from_line 
from_position = 0
try:
    with open(cursor_position, "r", encoding=ENCODING) as f:
        from_position = int(f.read())
except Exception as e:
    pass

# We read log_file_to_cut to put new lines in cut_file
with open(log_file_to_cut, "r", encoding=ENCODING) as f:
    with open(cut_file, "w", encoding=ENCODING) as fw:
        # We set cursor to the last position used (during last run of script)
        f.seek(from_position)
        for line in f:
            fw.write("%s" % (line))

    # We save the last position of cursor for next usage
    with open(cursor_position, "w", encoding=ENCODING) as fw:
        fw.write(str(f.tell()))

-2

大きなファイルを1行ずつ読み取る最良の方法は、Python 列挙関数を使用することです

with open(file_name, "rU") as read_file:
    for i, row in enumerate(read_file, 1):
        #do something
        #i in line of that line
        #row containts all data of that line

3
列挙型を使用する方が良いのはなぜですか?受け入れられた答えに対する唯一の利点は、OPが必要としないインデックスを取得し、コードを読みにくくすることです。
fuyas 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.