以下が好まれる理由は、まさに 1 つです。
with open('filename.txt') as fp:
for line in fp:
print line
私たちは皆、ガベージ コレクションのための CPython の比較的決定論的な参照カウント スキームに甘やかされています。with
他の仮想的な Python の実装では、メモリを再利用するために他のスキームを使用する場合、ブロックなしでファイルを「十分に迅速に」閉じるとは限りません。
このような実装では、ガベージ コレクターが孤立したファイル ハンドルに対してファイナライザーを呼び出すよりも速くファイルを開くと、OS から「開いているファイルが多すぎます」というエラーが発生する可能性があります。通常の回避策は、GC をすぐにトリガーすることですが、これは厄介なハックであり、ライブラリ内の関数を含め、エラーが発生する可能性のあるすべての関数で実行する必要があります。なんて悪夢だ。
with
または、ブロックを使用することもできます。
ボーナス質問
(質問の客観的な側面にのみ興味がある場合は、ここで読むのをやめてください。)
ファイルオブジェクトの反復子プロトコルにそれが含まれていないのはなぜですか?
これは API 設計に関する主観的な質問なので、2 つの部分に分けて主観的な回答をします。
直観的には、これは間違っているように感じます。なぜなら、イテレータ プロトコルが 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 つです。