withで安全に扱う
このレッスンで作るもの —
withを使って閉じ忘れの起きない保存処理を書き、読み戻した内容を返すsave_textを作ります。
with で安全に扱う
前の 2 回では、open で開いて close で閉じる、という手順を毎回書いてきました。この書き方には弱点があります。途中でエラーが起きると close にたどり着けない ことです。
f = open("kakeibo.txt", "w", encoding="utf-8")
f.write(str(price))
f.close()もし price の中身がおかしくて write の行で例外が出たら、その時点で関数を抜けてしまい、close は実行されません。ファイルは開きっぱなしになります。
開きっぱなしのファイルは、すぐには症状が出ません。書いたはずの内容が反映されない、同じファイルを二重に開けない、といった形であとから効いてきます。
with 文の書き方
Python にはこれを防ぐ専用の構文があります。with です。
with open("kakeibo.txt", "w", encoding="utf-8") as f:
f.write("りんご,120,食費")with open(...) as f と書き、そのブロックの中でファイルを使います。ブロックを抜けるときに、close が 自動で 呼ばれます。しかも途中で例外が出た場合でも必ず呼ばれます。
as f の f は、open が返したファイルオブジェクトに付ける名前です。with を使うと close の行が消えるので、コードも 1 行短くなります。
読み込みも同じ形です。
with open("kakeibo.txt", "r", encoding="utf-8") as f:
text = f.read()ブロックの外に出たあとも、text に入れた文字列はそのまま使えます。閉じられるのはファイルであって、変数の中身ではありません。
インデントの範囲がファイルの寿命
with のブロックは、インデントされている範囲です。ここを外すと、閉じたあとのファイルを触ることになります。
with open("kakeibo.txt", "r", encoding="utf-8") as f:
text = f.read()
lines = text.splitlines()f.read() はインデントの中、splitlines はブロックの外です。読み込み自体はブロックの中で終えて、加工は外でやる、という切り分けが読みやすくなります。
ブロックの中は短く保つのがコツです。ファイルとのやり取りだけを中に置き、計算や整形は外に出します。
動きを追ってみる
save_text("/tmp/kakeibo.txt", "りんご,120,食費") の流れを追います。
withで/tmp/kakeibo.txtを書きモードで開くwriteで"りんご,120,食費"を書く- ブロックを抜けるときに自動で閉じられる
- もう一度
withで同じパスを読みモードで開く readの結果を変数に入れ、ブロックを抜けてからreturnする
書きと読みで with を 2 回使います。1 つの with の中で書いて読むことはできません。書きモードで開いたファイルからは読めないからです。
よくある間違い
- コロンとインデントを忘れる —
with open(path, "w") as fの行末にはコロンが必要で、次の行はインデントします。忘れるとSyntaxErrorになります。 - ブロックの外で
fを使う — ブロックを抜けたあとにf.read()を呼ぶとValueErrorになります。閉じたファイルは読めません。 withを使ったのにcloseも書く — 動きはしますが二重に閉じる意味のない行です。withを使ったらcloseは書きません。
with は初めて見ると身構えますが、やっていることは「終わったら必ず片付ける」だけです。これ以降のレッスンでは、ファイルを開くときは常に with を使います。
なお with はファイル専用の構文ではありません。データベースの接続やネットワークの通信など、「使い終わったら必ず後始末が要るもの」全般に同じ形で使えます。今は open と組み合わせる形だけ覚えておけば十分ですが、この先ライブラリのサンプルコードで with を見かけたら、裏で自動の後始末が走っていると思ってください。
書き方の形をもう一度並べておきます。読みも書きも、変わるのはモードの文字だけです。
with open(path, "w", encoding="utf-8") as f:
f.write(text)
with open(path, "r", encoding="utf-8") as f:
saved = f.read()やってみよう
関数 save_text(path, text) を実装してください。with を使って path に text を書き出し、そのあともう一度 with で同じファイルを読み込み、読み戻した文字列を return します。
open には encoding="utf-8" を指定します。text が空文字のときは空文字が返り、改行を含む文字列を渡したときはそのまま返る形になっていれば正解です。close は書かずに、with に任せてください。
要件
- 関数名は save_text で、引数は path と text の 2 つ
- 書き込みと読み込みの両方で with 文を使う
- close は書かず、with に閉じる処理を任せる
- 読み戻した文字列を return する
- open には encoding="utf-8" を指定する
入出力例
save_text("/tmp/kakeibo_with_1.txt", "りんご,120,食費") → "りんご,120,食費"
save_text("/tmp/kakeibo_with_2.txt", "りんご,120,食費
みかん,80,食費") → "りんご,120,食費
みかん,80,食費"
save_text("/tmp/kakeibo_with_3.txt", "") → ""
save_text("/tmp/kakeibo_with_4.txt", "ノート,200,日用品
") → "ノート,200,日用品
"
save_text("/tmp/kakeibo_with_5.txt", "=== おこづかい帳 ===
合計 200円") → "=== おこづかい帳 ===
合計 200円"