web-dev-qa-db-ja.com

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

先史時代(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文でファイルを確実に閉じることができます...しかし、それがファイルオブジェクトのイテレータプロトコルに含まれていないのはなぜですか?

125
thebjorn

以下が好ましい理由は1つあります。

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

私たちは皆、CPythonの比較的決定的なガベージコレクションのための参照カウント方式に甘んじています。 Pythonの他の仮想的な実装は、他の方法でメモリを再利用するのであれば、withブロックなしでファイルを「素早く」閉じることは必ずしもできません。

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

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

ボーナス質問

(質問の客観的な側面にだけ興味があるならば、今読むのをやめてください。)

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

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

腸レベルでは、これは間違っているように感じます。それは、イテレータプロトコルがファイルハンドルを閉じるで閉じるという2つの別々のことをするからです。単純に見える関数に2つの動作をさせるのは良くない考えです。この場合、イテレータはファイルの内容に準機能的な値ベースの方法で関連しているため、特に問題がありますが、ファイルハンドルの管理は完全に別の作業です。目に見えないように、1つのアクションに両方を押しつぶすことは、コードを読む人間にとって驚くべきことであり、プログラムの振舞いについて推論することをより困難にします。

他の言語も基本的に同じ結論に達しました。 Haskellはファイルを繰り返し処理してストリームの終わりに到達したときに自動的に閉じることができる、いわゆる "lazy IO"と少しいじっていましたが、Haskellでlazy IOを使うことはほとんど普遍的にお勧めできません。そしてHaskellユーザーはほとんどConduitのようなより明示的なリソース管理に移行しました。これはPythonのwithブロックのように振舞います。

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

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

これはあまり一般的ではありませんが、最初の3行が元の既存のコードベースに3行目のコードを追加した可能性があることを考えてください。繰り返しがファイルを閉じたなら、私はそれをすることができないでしょう。そのため、反復とリソース管理を別々にしておくことで、コードのまとまりをより大きく機能するPythonプログラムにまとめることがより簡単になります。

コンポーザビリティは、言語またはAPIの最も重要なユーザビリティ機能の1つです。

213
Dietrich Epp

はい、

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

行く方法です。

冗長ではありません。もっと安全です。

19
eumiro

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

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
3
Lie Ryan