web-dev-qa-db-ja.com

Pythonで大きなファイルを1行ずつ読み込む方法

ファイル全体の各行を繰り返したい。これを行う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が表示されます。

助言がありますか?

目的はファイル内の各行を意味するペアワイズ文字列の類似性を計算することです、私は他のすべての行とLevenshtein距離を計算したいです。

489
384X21

ファイルを読み取るための正しい、完全にPythonicの方法は以下のとおりです。

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

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

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

1196
Katriel

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

  1. withの使用 - python 2.5以降からサポートされています
  2. 実際にどれだけの量を読むかを制御したい場合はyieldを使用します。

1. withの使用

withは、大きなファイルを読むための素晴らしい効率的なPythonicの方法です。利点 - 1)ファイルオブジェクトはwith実行ブロックから出た後に自動的に閉じられます。 2)withブロック内での例外処理3)memory 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()関数にも同じ)がファイル全体をメモリにロードしてから繰り返します。大きなファイルのための少し良い方法(最初に述べた2つの方法が最善です)は、以下のようにfileinputモジュールを使うことです:

import fileinput

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

fileinput.input()呼び出しは行を順番に読み込みますが、それらが読み込まれた後、あるいは単にそうさえしてもそれらをメモリに保持しません。なぜなら、pythonではfileは繰り返し可能だからです。

参考文献

  1. Python with statement
110

改行を取り除くには:

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

ユニバーサル改行サポートを使用する ファイル内のターミネータが'\n''\r''\n'のいずれであっても、すべてのテキストファイル行は'\r\n'で終了するようです。

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

  • 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でのみサポートされ、デフォルトはNoneです。 modeパラメータは、デフォルトで'r'になります。 UはPython 3では推奨されません。Windows上のPython 2では、\r\n\nに変換するための他のメカニズムがあります。

ドキュメント:

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

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

バイナリモードでも、ファイルをinの行に解析できます。各行には、ファイル内の終端文字がすべて含まれます。

@katrielalexanswer 、Pythonの open() doc、および iPython 実験のおかげで

35
Bob Stein

これはpythonでファイルを読むための可能な方法です:

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

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

16
Simon Bergot

私がどこから来たのかについてのいくつかの文脈。コードスニペットは最後にあります。

可能であれば、私は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の実際のボトルネックであり、ディスクではないことがわかります。

SATA6バスで回転するHDではなく、高速ビデオカードバスでSSDを使用し、さらに16個のCPUコアを使用します。

また、私が発見した別のテクニックは、1つの大きなファイルを多数のパーツファイルに事前に分割するのではなく、並列CSVファイルが1つの巨大ファイル内ですべて読み取り、各ワーカーをファイルにオフセットすることです。それぞれの並列ワーカーでpythonのファイルseek()およびtell()を使用して、ビッグテキストファイルを、ビッグファイル内の異なるバイトオフセットの開始バイト位置と終了バイト位置で、同時に同時に読み取ります。あなたはそのバイトに対して正規表現のfindallを行い、改行の数を返すことができます。これは部分的な合計です。ワーカーが終了した後にマップ関数が戻ったときに、最後に部分的な合計を合計してグローバル合計を取得します。

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

私は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 Gbpsのファイル整理速度です。それは回転するハードディスクではない、私の友人。それは実際には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

結論:純粋なpythonプログラムは、Cプログラムと比べて速度が速いです。ただし、少なくともラインカウントの目的で、純粋なpythonプログラムをCプログラムよりも使用するのは十分ではありません。一般的にこのテクニックは他のファイル処理にも使えるので、この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ファイルを読み取ることで、地球上のものよりも速く、速くなります。

9

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

しかしながら、あなたのアルゴリズムが進む方法では、ファイルの各行についてファイル全体を読み込みます。つまり、ファイル内の行数がNであれば、ファイルの読み取り全体の量、および Levenshtein距離の計算 - はN * Nで行われます。あなたはファイルサイズを心配していて、それをメモリに保存したくないので、 二次実行時間 を心配しています。あなたのアルゴリズムはO(n ^ 2)クラスのアルゴリズムの中にありますが、これは特殊化によってしばしば改善することができます。

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

あなたのファイルは何行ありますか、そしてあなたのアルゴリズムはどんな種類のマシン(memと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、または再利用のための中間結果のキャッシュ.

5
cfi
#Using a text file for the example
with open("yourFile.txt","r") as f:
    text = f.readlines()
for line in text:
    print line
  • 読むためにあなたのファイルを開いてください(r)
  • ファイル全体を読み、各行を list (text)に保存します。
  • 各行を印刷するリストをループします。

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

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

デフォルトのファイルロードは非常に遅いので使用しないことを強くお勧めします。あなたは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) 

データをチャンク単位でロードしてから行列演算を行う方が、要素ごとにデータを処理するよりもはるかに高速です。

2
John Haberstroh

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
KevinDTimm