web-dev-qa-db-ja.com

Python writelines()とwrite()の大きな時間差

ファイルのフォルダー(サイズはそれぞれ20 MBから100 MB)を読み取り、各行の一部のデータを変更し、ファイルのコピーに書き戻すスクリプトを作成していました。

_with open(inputPath, 'r+') as myRead:
     my_list = myRead.readlines()
     new_my_list = clean_data(my_list)
with open(outPath, 'w+') as myWrite:
     tempT = time.time()
     myWrite.writelines('\n'.join(new_my_list) + '\n')
     print(time.time() - tempT)
print(inputPath, 'Cleaning Complete.')
_

90 MBファイル(〜900,000行)でこのコードを実行すると、ファイルへの書き込みにかかる時間として140秒が出力されました。ここでは、writelines()を使用しました。そこで、ファイルの書き込み速度を改善するためのさまざまな方法を探しましたが、私が読んだほとんどの記事では、write()writelines()は1つの連結された文字列。また、次のステートメントのみにかかる時間も確認しました。

_new_string = '\n'.join(new_my_list) + '\n'
_

また、わずか0.4秒しかかからなかったため、リストの作成が原因ではありませんでした。試してみるwrite()私はこのコードを試しました:

_with open(inputPath, 'r+') as myRead:
     my_list = myRead.readlines()
     new_my_list = clean_data(my_list)
with open(outPath, 'w+') as myWrite:
     tempT = time.time()
     myWrite.write('\n'.join(new_my_list) + '\n')
     print(time.time() - tempT)
print(inputPath, 'Cleaning Complete.')
_

そして、2.5秒印刷しました。同じデータであるにもかかわらず、write()writelines()のファイル書き込み時間に大きな違いがあるのはなぜですか?これは通常の動作ですか、コードに何か問題がありますか?出力ファイルは両方のケースで同じように見えるので、データの損失がないことを知っています。

29
Arjun Balgovind

file.writelines()は、文字列のiterableを想定しています。その後、ループに進み、反復可能オブジェクトの各文字列に対してfile.write()を呼び出します。 Pythonでは、メソッドはこれを行います:

_def writelines(self, lines)
    for line in lines:
        self.write(line)
_

単一の大きな文字列を渡していますが、文字列も文字列の反復可能です。反復すると個々の文字、長さ1の文字列が得られます。したがって、実際にはlen(data)file.write()に個別に呼び出しています。また、書き込みバッファを一度に1文字ずつ作成しているため、速度が遅くなります。

file.writelines()に単一の文字列を渡さないでください。代わりにリストまたはTupleまたはその他の反復可能なものを渡します。

ジェネレーター式に改行を追加して、個別の行で送信できます。次に例を示します。

_ myWrite.writelines(line + '\n' for line in new_my_list)
_

ここで、clean_data() a generatorを作成し、クリーンな行を生成できる場合、入力ファイルからデータクリーニングジェネレーターを介して、使用せずに出力ファイルにデータをストリーミングできます。読み取りおよび書き込みバッファに必要な量より多くのメモリがありますが、行を消去するには多くの状態が必要です。

_with open(inputPath, 'r+') as myRead, open(outPath, 'w+') as myWrite:
    myWrite.writelines(line + '\n' for line in clean_data(myRead))
_

さらに、clean_data()を更新して、改行を含む行を出力することを検討します。

43
Martijn Pieters

martijnの答えを補完するものとして、最善の方法は、最初にjoinを使用してリストを作成しないようにすることです。

ジェネレータの内包表記をwritelinesに渡すだけで、最後に改行が追加されます。不要なメモリ割り当てやループはありません(内包表記以外)

myWrite.writelines("{}\n".format(x) for x in my_list)

'write(arg)'メソッドは引数として文字列を必要とします。したがって、一度呼び出すと、直接書き込みます。これがはるかに高速な理由です。 writelines()メソッドを使用しているかのように、イテレータとして文字列のリストが必要です。そのため、writelinesにデータを送信している場合でも、イテレータが取得されたと見なし、反復処理を試みます。そのため、イテレータであるため、イテレートして記述するには時間がかかります。

それは明らかですか?

2
nanithehaddock