メモリにロードせずに、Pythonで大きなテキストファイルを1行ずつ読み取るにはどうすればよいですか?


238

大きなファイルを1行ずつ読み取る必要があります。ファイルのサイズが5GBを超えていて、各行を読み取る必要があるとしましょう。ただしreadlines()、メモリ内に非常に大きなリストが作成されるため、使用したくありません。

この場合、以下のコードはどのように機能しますか?されxreadlines、それ自体がメモリに一つずつ読んで?ジェネレータ式は必要ですか?

f = (line for line in open("log.txt").xreadlines())  # how much is loaded in memory?

f.next()  

さらに、Linux tailコマンドと同じように、これを逆の順序で読み取るにはどうすればよいですか?

私が見つけた:

http://code.google.com/p/pytailer/

そして

" pythonの頭、尾、テキストファイルの行で逆読み "

両方とも非常にうまくいきました!


そして、私はこれを尾から読むために何ができますか?最後の行から始めて、行ごとに。
ブルーノロシャ-rochacbruno

これは別の質問である必要があります
cmcginty '25 / 06/25

回答:


308

キースは簡潔ですが、ファイルを明示的に閉じないため、この回答を提供しました

with open("log.txt") as infile:
    for line in infile:
        do_something_with(line)

30
それでも質問は、「for line in infile」は私の5GBの行をメモリにロードしますか?そして、どうやって尾から読むことができますか?
Bruno Rocha-rochacbruno

66
@rochacbruno、一度に1行しか読みません。次の行が読み取られると、その行への参照を別の場所に保存していない限り、前の行はガベージコレクションされます
John La Rooy

1
@rochacbruno、残念ながら効率的に行を逆順に読むのは簡単ではありません。一般的には、ファイルの終わりから
適切

4
ありがとう!私は尾ソリューション見つけstackoverflow.com/questions/5896079/...
rochacbruno -ブルーノ・ロシャ

1
@bawejakunal、行が長すぎて一度にメモリに読み込めないということですか?これは、テキストファイルでは珍しいことです。for行を反復するループを使用する代わりに、を使用chunk = infile.read(chunksize)して、その内容に関係なく、限られたサイズのチャンクを読み取ることができます。チャンク内で改行を自分で検索する必要があります。
John La Rooy、2018年

60

必要なことは、ファイルオブジェクトをイテレータとして使用することだけです。

for line in open("log.txt"):
    do_something_with(line)

さらに良いのは、最近のPythonバージョンでコンテキストマネージャを使用することです。

with open("log.txt") as fileobject:
    for line in fileobject:
        do_something_with(line)

これにより、ファイルも自動的に閉じます。


2
それはメモリにファイル全体をロードしていませんか?
ブルーノロシャ-rochacbruno

17

古い学校のアプローチ:

fh = open(file_name, 'rt')
line = fh.readline()
while line:
    # do stuff with line
    line = fh.readline()
fh.close()

2
マイナーな注意:例外の安全性のために、 'with'ステートメントを使用することをお勧めします。あなたのケースでは、 "with open(filename、 'rt')as fh:"
prokher

16
@prokher:ええ、でも私はこれを「古い学校」と呼んでいました。
PTBNL、2015年

15

代わりにイテレータを使用するほうがよいでしょう。関連: http //docs.python.org/library/fileinput.html

ドキュメントから:

import fileinput
for line in fileinput.input("filename"):
    process(line)

これにより、ファイル全体を一度にメモリにコピーする必要がなくなります。


ドキュメントはスニペットを「典型的な使用」として示していますが、これを使用しても、ループが終了したときにclose()返されたFileInputクラスオブジェクトのメソッドは呼び出されません-したがって、この方法での使用は避けます。Python 3.2では、最終的にfileinputこの問題に対処するコンテキストマネージャープロトコルとの互換性を確保しました(ただし、コードはまだ示されている方法で作成されていません)。
martineau

7

ファイルに改行がない場合は、次のようにします。

with open('large_text.txt') as f:
  while True:
    c = f.read(1024)
    if not c:
      break
    print(c)

私はこの方法が好きですが、テキストの行がチャンクに分割されるリスクがあります。私はこれを個人的に見ました。つまり、私がそうであるようにファイル内のsstringを検索している場合、それらがあった行がチャンクに分割されていたため、いくつか見落とすことになります。これを回避する方法はありますか?ミスカウント@Ariel Cabib
edo101

6

これを試してください:

with open('filename','r',buffering=100000) as f:
    for line in f:
        print line

説明してください?
Nikhil VJ 2018

3
Pythonの公式docmunetsから:リンク オプションのバッファリング引数は、ファイルの目的のバッファサイズを指定します。0はバッファリングされていない、1はラインバッファリングされている、その他の正の値は(およそ)そのサイズ(バイト単位)のバッファを使用することを意味します。ネガティブバッファリングとは、システムデフォルトを使用することを意味します。これは通常、ttyデバイスではラインバッファリングされ、他のファイルでは完全にバッファリングされます。省略した場合、システムのデフォルトが使用されます
jyoti das

私の場合、2つのファイルハンドラー(1つは読み取り、もう1つは書き込み)を備えた>〜4gbのファイルを使用して私の日を節約しました。ありがとう。
Xelt

@jyotidasこの方法は好きですが、テキストの行がチャンクに分割されるリスクがあります。私はこれを個人的に見ました。つまり、私がそうであるようにファイル内のsstringを検索している場合、それらがあった行がチャンクに分割されていたため、いくつか見落とすことになります。これを回避する方法はありますか?ミスカウントが発生したため、readlinesを使用してもうまくいきませんでした
edo101

3

@ john-la-rooyの回答がそれを思わせるほど簡単だったとは信じられませんでした。そこで、cp行ごとの読み書きを使用してコマンドを再作成しました。クレイジーファーストです。

#!/usr/bin/env python3.6

import sys

with open(sys.argv[2], 'w') as outfile:
    with open(sys.argv[1]) as infile:
        for line in infile:
            outfile.write(line)

注:Pythonはreadline行末を標準化しているため、DOSの行末がのドキュメント\r\nをUnixの行末にに変換するという副作用があり\nます。このトピックを検索した理由は、行末のごちゃごちゃしたログファイルを変換する必要があったためです(開発者がさまざまな.NETライブラリを盲目的に使用したためです)。最初の速度テストの後でrstrip、ラインに戻る必要がないことにショックを受けました。もう完璧でした!
Bruno Bronosky 2017

2

のプロジェクトは、過去6年間の長い道のりを歩んできました。それはパンダ機能の有用なサブセットをカバーするシンプルなAPIを持っています。

dask.dataframeは内部でチャンクを処理し、多くの並列処理可能な操作をサポートし、スライスをパンダに簡単にエクスポートして、メモリ内の操作に使用できます。

import dask.dataframe as dd

df = dd.read_csv('filename.csv')
df.head(10)  # return first 10 rows
df.tail(10)  # return last 10 rows

# iterate rows
for idx, row in df.iterrows():
    ...

# group by my_field and return mean
df.groupby(df.my_field).value.mean().compute()

# slice by column
df[df.my_field=='XYZ'].compute()

2

メモリの問題を引き起こさずに任意のサイズのテキストファイルをロードするためのコードを示します。 ギガバイトサイズのファイルをサポートしています

https://gist.github.com/iyvinjose/e6c1cb2821abd5f01fd1b9065cbc759d

ファイルdata_loading_utils.pyをダウンロードして、コードにインポートします

使用法

import data_loading_utils.py.py
file_name = 'file_name.ext'
CHUNK_SIZE = 1000000


def process_lines(data, eof, file_name):

    # check if end of file reached
    if not eof:
         # process data, data is one single line of the file

    else:
         # end of file reached

data_loading_utils.read_lines_from_file_as_data_chunks(file_name, chunk_size=CHUNK_SIZE, callback=self.process_lines)

process_linesメソッドはコールバック関数です。これは、一度にファイルの1つの行を表すパラメーターデータを使用して、すべての行に対して呼び出されます。

マシンのハードウェア構成に応じて、変数CHUNK_SIZEを構成できます。


私はこの方法が好きですが、テキストの行がチャンクに分割されるリスクがあります。私はこれを個人的に見ました。つまり、私がそうであるようにファイル内のsstringを検索している場合、それらがあった行がチャンクに分割されていたため、いくつか見落とすことになります。これを回避する方法はありますか?ミスカウントが発生したため、readlinesを使用してもうまくいきませんでした
edo101

0

これはどう?ファイルをチャンクに分割し、1行ずつ読み取ります。これは、ファイルを読み取ると、オペレーティングシステムが次の行をキャッシュするためです。ファイルを1行ずつ読み取る場合は、キャッシュされた情報を効率的に使用していません。

代わりに、ファイルをチャンクに分割し、チャンク全体をメモリにロードしてから、処理を実行します。

def chunks(file,size=1024):
    while 1:

        startat=fh.tell()
        print startat #file's object current position from the start
        fh.seek(size,1) #offset from current postion -->1
        data=fh.readline()
        yield startat,fh.tell()-startat #doesnt store whole list in memory
        if not data:
            break
if os.path.isfile(fname):
    try:
        fh=open(fname,'rb') 
    except IOError as e: #file --> permission denied
        print "I/O error({0}): {1}".format(e.errno, e.strerror)
    except Exception as e1: #handle other exceptions such as attribute errors
        print "Unexpected error: {0}".format(e1)
    for ele in chunks(fh):
        fh.seek(ele[0])#startat
        data=fh.read(ele[1])#endat
        print data

これは有望に見えます。これはバイト単位で読み込まれますか、それとも行単位で読み込まれますか?バイト単位の場合、行が壊れるのではないかと思います。たとえば、一度に1000行をロードして処理するにはどうすればよいですか。
Nikhil VJ 2018

0

ありがとうございました!私は最近python 3に変換し、readlines(0)を使用して大きなファイルを読み取ることに不満を感じています。これで問題は解決しました。しかし、各行を取得するには、いくつかの追加の手順を実行する必要がありました。各行の前には「b '」があり、これはバイナリ形式であったと思います。「decode(utf-8)」を使用すると、ASCIIに変更されました。

次に、各行の中央にある「= \ n」を削除する必要がありました。

次に、行を新しい行で分割します。

b_data=(fh.read(ele[1]))#endat This is one chunk of ascii data in binary format
        a_data=((binascii.b2a_qp(b_data)).decode('utf-8')) #Data chunk in 'split' ascii format
        data_chunk = (a_data.replace('=\n','').strip()) #Splitting characters removed
        data_list = data_chunk.split('\n')  #List containing lines in chunk
        #print(data_list,'\n')
        #time.sleep(1)
        for j in range(len(data_list)): #iterate through data_list to get each item 
            i += 1
            line_of_data = data_list[j]
            print(line_of_data)

これがアロヒのコードの「印刷データ」のすぐ上から始まるコードです。


0

この他の質問で、並列バイトレベルのランダムアクセスアプローチを示しました。

readlinesなしでテキストファイルの行数を取得する

すでに提供されている回答のいくつかは、簡潔で簡潔です。それらのいくつかが好きです。しかし、それは実際には、ファイル内のデータをどのように処理するかによって異なります。私の場合、大きなテキストファイルでできるだけ速く行を数えたかっただけです。もちろん、コードを変更して、他のコードを実行することもできます。


0

これに関して私が見つけた最良の解決策であり、330 MBのファイルで試しました。

lineno = 500
line_length = 8
with open('catfour.txt', 'r') as file:
    file.seek(lineno * (line_length + 2))
    print(file.readline(), end='')

ここで、line_lengthは1行の文字数です。たとえば、「abcd」の行の長さは4です。

「\ n」文字をスキップして次の文字に移動するために、行の長さに2を追加しました。


-1

これは、並行して作業し、データのチャンクのみを読み取るが、新しい行でクリーンに保つ場合に役立ちます。

def readInChunks(fileObj, chunkSize=1024):
    while True:
        data = fileObj.read(chunkSize)
        if not data:
            break
        while data[-1:] != '\n':
            data+=fileObj.read(1)
        yield data

-10
f=open('filename','r').read()
f1=f.split('\n')
for i in range (len(f1)):
    do_something_with(f1[i])

お役に立てれば。


5
これはメモリ内のファイル全体を読み取らないでしょうか?質問はそれをどのように回避するかを明確に尋ねているため、これは質問の答えにはなりません。
フェルミパラドックス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.