CHAPTER 7 ・ 品質を守る / Lesson 29
安全に動かす習慣
本番の前に確認する dry-run、元データを守るバックアップ、何度動かしても同じ結果になる作り方です。
このレッスンが終わったら
「実行の前に、何が起きるかを表示する。元ファイルは書き換えず、同じ処理を2回動かしても困らない作りにする」と言える。
動かしてから、気づいた
invoices フォルダに、請求書のファイルが3つあります(invoice_tanaka.txt など)。これを「2025-10_ から始まる名前」にそろえよう、というスクリプトを書きました。うっかり、新しい名前に人の名前を入れ忘れた版が、次のコードです。実行すると、どうなるでしょうか。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
3つあった請求書が、1つになりました。3つとも同じ名前(2025-10.txt)に変えようとして、あとから来たファイルが、先のファイルを上書きしたためです。佐藤さんと鈴木さんの請求書は、もうありません。しかも、エラーは1つも出ませんでした。(Windows では同じ名前があるとエラーで止まりますが、Mac などでは、このように黙って上書きされます。)
自動化したスクリプトは、人が手でやるより速く、大量に、確認なしに動きます。だから、間違えたときの被害も、大きくなります。この回では、本番で動かす前の安全の習慣を身につけます。
まず「表示するだけ」で動かす
最初の習慣は、ドライランです。実際には何も変えず、「何をするつもりか」だけを表示します。同じスクリプトを、安全な作りに書き直しました。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
「旧名 -> 新名」の一覧が出て、最後に表示したフォルダの中身は、元のままです。一覧を目で確かめ、問題がなければ、DRY_RUN = True を False に書き換えて、もう一度実行してみましょう。名前が変わり、バックアップの件数が表示されます。
仕組みを読む
安全な手順は、次の流れです。本番(実際に変える)が、最後から2番目になっているところが、ポイントです。
- 対象を一覧にする件数と名前を表示
- チェックする重複・既存を点検
- ドライラン表示だけ。目で確認
- バックアップ→本番コピーを取ってから変更
- 検算する件数・合計が合うか
DRY_RUN = True「表示するだけ」のスイッチ。まずは True で動かしますplan = [(旧, 新), ...]変更の計画を先に作ります。この時点では、ファイルに触れませんassert len(new_names) == len(set(new_names))新しい名前に重複がないか確かめます(set は Lesson 07、assert は Lesson 28 で学んだ通りです)assert not any(new.exists() ...)同じ名前のファイルが、すでにないか確かめますif not DRY_RUN:本番のときだけ、この中を実行しますshutil.copytree(folder, "invoices_backup")変更の前に、フォルダごとコピーして取っておきます日本語にすると
変えて、試す
① 既存のファイルを、上書きしない。 ファイルを開くときの指定(モード)が、"w" だと、同じ名前のファイルを黙って上書きします。"x" にすると、すでにあるときは FileExistsError で止まります。また shutil.copy は、ファイルをコピーする命令で、コピーに対して作業すれば、元のデータは守られます。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
2回目で FileExistsError と出て、1回目の report.txt は無事です。「消してはいけないものは、"x" で守る」と覚えておきましょう。
② 何度動かしても、同じ結果にする。 同じスクリプトを、うっかり2回動かすことは、実務でよくあります。2回動かしても結果が変わらないことを、冪等性(べきとうせい)と呼びます。ファイルに追記する("a")と、2回で内容が倍になります。毎回、上書きして作り直す("w")なら、何回でも同じです。
▶ 実行を押すと、ここに結果が出ます
追記は2行、上書きは1行です。フォルダを作る mkdir も、exist_ok=True と付ければ、2回目でもエラーになりません。名前変更のスクリプトも、1回目で全部変えてしまえば、2回目は対象が0件になるだけで、何も壊れません。
③ 結果を検算し、後片付けを確実にする。 処理の最後に、元データの合計と、出力の合計が一致するかを確かめます。また、途中で作った作業用ファイルは、エラーが起きても消えるよう finally(Lesson 09)に書きます(unlink は、ファイルを削除する命令です)。
この欄では、サンプルファイルが使えます。実行のたびに、ファイルは最初の状態に戻ります。
▶ 実行を押すと、ここに結果が出ます
自分で書く
お題① 仮ファイルを消す関数 delete_files を、ドライラン対応にしてください。dry_run が True のときは「削除予定: 名前」と表示するだけにします。False のときは、Path(name).unlink() で削除します。最後に、残っている .tmp ファイルの数を表示します。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
for の中で if dry_run: と書き、print(f"削除予定: {name}") を実行します。else: に Path(name).unlink() を書きます。呼び出しは dry_run を省略しているので、初期値の True で動きます。
答えの例を見る
from pathlib import Path
for name in ["a.tmp", "b.tmp"]:
Path(name).write_text("仮ファイル", encoding="utf-8")
def delete_files(names, dry_run=True):
for name in names:
if dry_run:
print(f"削除予定: {name}")
else:
Path(name).unlink()
delete_files(["a.tmp", "b.tmp"])
print(len(list(Path(".").glob("*.tmp"))))お題② 次の write_summary は、2回動かすと、ファイルに同じ行が2つ入ります。何回動かしても1行になるように直してください。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
ファイルを開くときのモードを、追記の "a" から、毎回作り直す "w" に変えます。
答えの例を見る
from pathlib import Path
def write_summary(total):
with open("summary.txt", "w", encoding="utf-8") as f:
f.write(f"合計 {total}\n")
write_summary(100)
write_summary(100)
print(len(Path("summary.txt").read_text(encoding="utf-8").splitlines()))お題③ save_report は、すでに report.txt があっても上書きしてしまいます。"x" モードで開き、すでにあるときは FileExistsError を except で受けて、「すでにあります。上書きしません」と表示するようにしてください。
▶ 実行を押すと、ここに結果が出ます
ヒントを見る
open の "w" を "x" に変え、try: の中に with ... と print("作成しました") を入れます。except FileExistsError: で、メッセージを表示します。
答えの例を見る
def save_report(text):
try:
with open("report.txt", "x", encoding="utf-8") as f:
f.write(text)
print("作成しました")
except FileExistsError:
print("すでにあります。上書きしません")
save_report("9月分")
save_report("9月分(やり直し)")仕事ならどう使う?
理解チェックドライラン(DRY_RUN = True)の目的は?
理解チェック月次の集計ファイルを作るスクリプトを、うっかり2回動かしました。結果が変わらず安心なのは、どの作り方でしょう?