以下が推奨される理由は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つです。