Pythonでファイルを1行ずつ読み取るにはどうすればよいですか?


137

先史時代(Python 1.4)には、次のことを行いました。

fp = open('filename.txt')
while 1:
    line = fp.readline()
    if not line:
        break
    print line

Python 2.1以降では、次のようにしました。

for line in open('filename.txt').xreadlines():
    print line

Python 2.3で便利なイテレータプロトコルを入手する前に、次のことができます。

for line in open('filename.txt'):
    print line

より冗長な例をいくつか見ました。

with open('filename.txt') as fp:
    for line in fp:
        print line

これは、今後推奨される方法ですか?

[編集] withステートメントでファイルを確実に閉じることができますが、なぜそれがファイルオブジェクトのイテレータプロトコルに含まれていないのですか?


4
imho、最後の提案は以前のものより冗長ではありません。それはより多くの仕事をするだけです(完了したらファイルが確実に閉じられるようにします)。
azhrei 2012

1
@azhreiは1行多いため、客観的にはより冗長です。
thebjorn '19

7
私はあなたの言っていることを理解していますが、リンゴとリンゴを比較しているだけです。投稿の最後から2番目の提案には、最後のオプションと同じように例外処理コードも必要です。したがって、実際にはより冗長になります。それは、最後の2つのオプションのどちらが実際に最適であるかによって異なります。
azhrei 2012

回答:


227

以下が推奨される理由は1つだけです。

with open('filename.txt') as fp:
    for line in fp:
        print line

私たちは皆、ガベージコレクションのためのCPythonの比較的確定的な参照カウントスキームに甘んじています。その他、架空のPythonの実装でwithは、他のスキームを使用してメモリを再利用する場合、ブロックなしでファイルを必ずしも「すぐに」閉じるとは限りません。

このような実装では、ガベージコレクターが孤立したファイルハンドルでファイナライザーを呼び出すよりも速くコードがファイルを開くと、OSから「多すぎるファイルを開く」エラーが発生する可能性があります。通常の回避策は、GCをすぐにトリガーすることですが、これは厄介なハックであり、ライブラリ内のものも含めて、エラーが発生する可能性のあるすべての関数で実行する必要があります。なんて悪夢なんだ。

または、withブロックを使用することもできます。

ボーナス質問

(質問の客観的な側面のみに関心がある場合は、今すぐ読み進めないでください。)

なぜそれがファイルオブジェクトのイテレータプロトコルに含まれていないのですか?

これはAPI設計に関する主観的な質問なので、主に2つの部分で答えます。

直感のレベルでは、イテレータプロトコルが2つの別々のこと(行反復してファイルハンドル閉じる)を実行するため、これは間違っているように感じられます。この場合、イテレータはファイルの内容に準機能的な値ベースの方法で関連付けられるため、特に気分が悪くなりますが、ファイルハンドルの管理は完全に別のタスクです。両方を、目に見えない形で1つのアクションにつぶすことは、コードを読む人間にとって驚くべきことであり、プログラムの動作について推論することをより困難にします。

他の言語も基本的に同じ結論に達しています。Haskellは、いわゆる「レイジーIO」で短時間いじめられました。これにより、ファイルを反復処理して、ストリームの最後に到達したときに自動的に閉じることができますが、最近のHaskellでレイジーIOを使用することはほとんどありません。ユーザーは主にwith、Python のブロックのように動作するConduitのようなより明示的なリソース管理に移行しています。

技術的なレベルでは、Pythonでファイルハンドルを使用して実行したいことがいくつかあります。これは、反復によりファイルハンドルが閉じられた場合には機能しません。たとえば、ファイルを2回繰り返す必要があるとします。

with open('filename.txt') as fp:
    for line in fp:
        ...
    fp.seek(0)
    for line in fp:
        ...

これはあまり一般的ではない使用例ですが、元々上部の3行があった既存のコードベースに下部の3行のコードを追加した可能性があるという事実を考慮してください。イテレーションがファイルを閉じた場合、私はそれを行うことができません。したがって、反復とリソース管理を別々にしておくと、コードのチャンクをより大きく機能するPythonプログラムに簡単に構成できます。

構成可能性は、言語またはAPIの最も重要なユーザビリティ機能の1つです。


1
+1は、opに対する私のコメントの「いつ」を説明するためです;-)
azhrei

代替実装を使用しても、withハンドラーは、数百のファイルを非常に迅速に連続して開くプログラムで問題を引き起こすだけです。ほとんどのプログラムは、ぶら下がりファイル参照を問題なく使用できます。無効にしない限り、最終的にはGCが起動してファイルハンドルをクリアします。withただし、安心できるので、それでもベストプラクティスです。
リーライアン

1
@DietrichEpp:おそらく「ダングリングファイルリファレンス」は適切な言葉ではありませんでした。実際には、アクセスできなくなったがまだ閉じていないファイルハンドルを意味していました。いずれの場合でも、GCはファイルオブジェクトを収集するときにファイルハンドルを閉じます。そのため、ファイルオブジェクトへの追加の参照がなく、GCを無効にせず、多くのファイルをすばやく開いていない場合に限ります。連続すると、ファイルを閉じないため、「ファイルが多すぎて開かない」可能性は低くなります。
リーライアン

1
そうです、「ガベージコレクターが孤立したファイルハンドルでファイナライザーを呼び出すよりも速くコードがファイルを開く場合」とは、まさにその意味です。
ディートリッヒエップ2012

1
で使用する最大の理由は、ファイルを閉じないと、すぐに書き込まれるとは限らないためです。
アンチモン2014

20

はい、

with open('filename.txt') as fp:
    for line in fp:
        print line

行く方法です。

より冗長ではありません。より安全です。


5

追加の行でオフになっている場合は、次のようなラッパー関数を使用できます。

def with_iter(iterable):
    with iterable as iter:
        for item in iter:
            yield item

for line in with_iter(open('...')):
    ...

Python 3.3では、yield fromステートメントはこれをさらに短くします。

def with_iter(iterable):
    with iterable as iter:
        yield from iter

2
関数xreadlines ..を呼び出し、それをxreadlines.pyという名前のファイルに入れれば、Python 2.1構文に戻ります:-)
thebjorn

@thebjorn:たぶん、しかし、あなたが引用したPython 2.1の例は、別の実装で閉じられていないファイルハンドラから安全ではありませんでした。閉じられていないファイルハンドラから安全なPython 2.1ファイルの読み取りには、少なくとも5行かかります。
リーライアン

-2
f = open('test.txt','r')
for line in f.xreadlines():
    print line
f.close()

5
これは実際には質問の答えにはなりません
Thayne
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.