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 ff は、open が返したファイルオブジェクトに付ける名前です。with を使うと close の行が消えるので、コードも 1 行短くなります。

読み込みも同じ形です。

with open("kakeibo.txt", "r", encoding="utf-8") as f: text = f.read()

ブロックの外に出たあとも、text に入れた文字列はそのまま使えます。閉じられるのはファイルであって、変数の中身ではありません。

diagram (will load when visible)

インデントの範囲がファイルの寿命

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,食費") の流れを追います。

  1. with/tmp/kakeibo.txt を書きモードで開く
  2. write"りんご,120,食費" を書く
  3. ブロックを抜けるときに自動で閉じられる
  4. もう一度 with で同じパスを読みモードで開く
  5. read の結果を変数に入れ、ブロックを抜けてから return する

書きと読みで with を 2 回使います。1 つの with の中で書いて読むことはできません。書きモードで開いたファイルからは読めないからです。

よくある間違い

  1. コロンとインデントを忘れるwith open(path, "w") as f の行末にはコロンが必要で、次の行はインデントします。忘れると SyntaxError になります。
  2. ブロックの外で f を使う — ブロックを抜けたあとに f.read() を呼ぶと ValueError になります。閉じたファイルは読めません。
  3. 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 を使って pathtext を書き出し、そのあともう一度 with で同じファイルを読み込み、読み戻した文字列を return します。

open には encoding="utf-8" を指定します。text が空文字のときは空文字が返り、改行を含む文字列を渡したときはそのまま返る形になっていれば正解です。close は書かずに、with に任せてください。

要件

  1. 関数名は save_text で、引数は path と text の 2 つ
  2. 書き込みと読み込みの両方で with 文を使う
  3. close は書かず、with に閉じる処理を任せる
  4. 読み戻した文字列を return する
  5. 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円"

ヒント

生田 陸人
ゆめさくエンジニア / 現役ソフトウェアエンジニア
編集 LuaGate編集部
main.py
main.py
学習モード

メモ

withで安全に扱う

⌘S で保存