先史時代(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文でファイルを確実に閉じることができます...しかし、それがファイルオブジェクトのイテレータプロトコルに含まれていないのはなぜですか?
以下が好ましい理由は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つです。
はい、
with open('filename.txt') as fp:
for line in fp:
print line
行く方法です。
冗長ではありません。もっと安全です。
余分な行でオフになっている場合は、次のようにラッパー関数を使用できます。
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