Pythonで記録アプリを作る|閉じても消えないtkinterアプリ10本(JSON・CSV保存)
入門を終えて最初に作りたくなるのが、毎日つける小さな記録アプリです。買い物リスト・気分・体重・習慣など10本の完成コードを、データをいつ・どこへ・どんな形式で保存しているかで比べました。作る1本を決めて、そのままコードを持ち帰れます。
1. 結論:最初は買い物リスト、次に気分トラッカー
記録アプリで初心者がつまずくのは、画面よりも保存です。閉じた瞬間に消えるなら、毎日使う道具になりません。この記事では当サイトの記録・管理系アプリのうち10本を、保存のしかたと日付の扱いで比べました。
① 買い物リスト(★☆☆・171行)で「閉じても残る」を体験する → ② 気分トラッカー(★☆☆・170行)でCSV保存と今日の日付、簡単なグラフを足す → ③ 習慣トラッカーか体重記録アプリ(どちらも★★☆)で日付の計算に進む。①は操作のたびに自動でJSONへ書き込むので、保存の基本形がそのまま読めます。
10本のうち、起動時に前回のデータを自動で読み込むのは6本です。3本は保存ボタンで書き出すだけで自動では読み込まず、デイリープランナーは保存しません。
2. 10本の保存方式と日付の扱い(一覧表)
行数は配布している.pyファイルの実際の行数(空行・コメント込み)、難易度は当サイトの3段階表記です。保存方式と日付の列は、コードを読んで書きました。
| アプリ | 保存の形式とタイミング | 日付の扱い | 主なウィジェット | 行数 | 難易度 | コード |
|---|---|---|---|---|---|---|
| 買い物リスト | JSON・変更のたびに自動 | なし | ttk.Treeview/Combobox(絞り込み) | 171 | ★☆☆ | app069.py |
| 時間割ビューアー | JSON・セル編集のたびに自動 | なし(月〜金×6限の表) | Label(クリックで編集)/simpledialog | 121 | ★★☆ | app082.py |
| ペット健康ノート | JSON・追加と削除で自動+書き出し | 入力した日付で並べ替え | Treeview/Canvas(体重グラフ) | 221 | ★★☆ | app096.py |
| 気分トラッカー | CSV・記録のたびに全件を書き直す | 今日の日付で記録 | Radiobutton/Canvas(推移グラフ) | 170 | ★☆☆ | app034.py |
| フラッシュカード | JSON・カード追加時だけ自動 | なし | Label(カード面)/Entry | 146 | ★★☆ | app025.py |
| チェックリスト | JSON・保存ボタン(ホームにも複製) | なし | Listbox/ttk.Progressbar | 182 | ★☆☆ | app081.py |
| 体重記録アプリ | JSON・保存ボタン | 同じ日付は上書き | Treeview/Canvas(折れ線) | 204 | ★★☆ | app061.py |
| お小遣い帳 | CSV・保存ボタン | 入力した日付で並べ替え | Treeview/Combobox | 225 | ★★☆ | app064.py |
| 習慣トラッカー | CSV・保存ボタン | 今日までの7日間を計算 | Checkbutton(7日×習慣の格子) | 163 | ★★☆ | app062.py |
| デイリープランナー | 保存しない | 今日の日付と曜日を表示 | ttk.Notebook(3タブ)/Text | 151 | ★★☆ | app035.py |
使っているのはtkinter・json・csv・datetime・os・randomだけで、すべて標準ライブラリです(pip install不要。仕様は公式ドキュメントのjson・csv)。部品の役割はtkinterウィジェット一覧で確かめられます。
10本とも、Windows 11(実機)で起動と基本操作を、Linux Mint 22.3(仮想マシン)で起動と画面表示を確認しています。全機能を網羅したテストではなく、macOSでは確認していません。
3. 閉じても記録が残る仕組み:起動時に読み、変更のたびに書く
骨組みは、起動時にファイルがあれば読み込む_load()と、リストを丸ごと書き出す_save()の2つです。買い物リストのコードから抜き出しました(エラー処理は省略)。
SAVE_FILE = "shopping_list.json"
def _load(self):
if os.path.exists(self.SAVE_FILE):
with open(self.SAVE_FILE, "r", encoding="utf-8") as f:
self.items = json.load(f)
def _save(self):
with open(self.SAVE_FILE, "w", encoding="utf-8") as f:
json.dump(self.items, f, ensure_ascii=False, indent=2)
_save()は追加・チェック切り替え・削除・完了済みの一括削除の4か所すべてから呼ばれるので、保存ボタンがなくても前回のリストが残ります。
試すときは、商品を2つほど追加してウィンドウを閉じ、もう一度起動してください。shopping_list.jsonができていて、メモ帳で開くと商品ごとに"name"・"category"・"priority"・"checked"の4項目が並んでいます。ensure_ascii=Falseがないと日本語は\u725bのような記号の列になり、indent=2がないと1行に詰まります。
同じ「変更のたびに保存」でも、残りの4本は保存するきっかけに差があります。
- 時間割ビューアー:月〜金×6限の表を二重リスト(
data[限][曜日])のままJSONにします。セルをクリックするとsimpledialog.askstringの小窓が開き、授業名を入れた時点で保存されます - ペット健康ノート:記録の追加と削除で自動保存します。「エクスポート」は別の場所へコピーを作る機能です
- 気分トラッカー:「記録する」を押すたびにCSVへ書き出します(§6)
- フラッシュカード:保存するのはカードを追加したときだけです。シャッフルした順番は次にカードを足すまでファイルに反映されず、カードを消すボタンもありません
改造するときは、データを書き換える関数をすべて洗い出し、それぞれの最後で_save()を呼んでいるかを確かめてください。1か所でも抜けると、その操作だけ次の起動で元に戻ります。
4. 保存ボタン式の4本と、保存しないデイリープランナー
残りの5本のうち4本は、保存ボタンを押さない限りファイルに書きません。押すとfiledialog.asksaveasfilenameの保存画面で置き場所と名前を選べるので、月ごとにファイルを分けられます。代わりに、押し忘れて閉じると入力が消えます。
- 体重記録アプリ:保存も読み込みも手動で、起動時の自動読み込みはありません。毎回「JSONから読込」から始めます
- お小遣い帳:CSVで保存・読込します。読んだ行は今の一覧に
extendで付け足されるので、同じファイルを2回読むと取引が2倍に並びます - 習慣トラッカー:CSVで保存・読込します。保存するのは今日までの7日分だけです(§8)
- チェックリスト:「保存」を押すと、選んだファイルに加えてホームフォルダの
.checklist.jsonにも書き、起動時はこちらを自動で読み込みます。保存画面でキャンセルするとホームのファイルも更新されません
押し忘れを防ぐなら、閉じるボタンに保存を割り込ませます。root.protocol("WM_DELETE_WINDOW", self._on_close)と登録し、_on_close()で決まったファイルへ保存してからroot.destroy()を呼ぶ形です。
読み込みは、体重記録・チェックリスト・習慣トラッカーが今のデータとの置き換え、お小遣い帳だけが付け足しです。
デイリープランナーは、10本の中で唯一ファイルを使いません。jsonもopen()も出てこず、スケジュール・やること・メモの3タブの内容はウィンドウを閉じると消えます。その日だけの下書き用と割り切るか、§3の_load()・_save()を足す練習台にしてください。スケジュール欄はListboxに文字列を直接入れているので、先にリストで持つ形へ直します。
5. 保存ファイルはどこにできるのか
「保存したはずのファイルが見つからない」は、記録アプリでつまずきやすい点です。保存する9本の保存先の決め方は3通りでした。
| 保存先の決め方 | アプリとファイル名 |
|---|---|
| ファイル名だけを書いている(実行したときのカレントディレクトリにできる) | 買い物リストshopping_list.json/時間割timetable.json/ペット健康ノートpet_health.json/気分トラッカーmood_log.csv/フラッシュカードflashcards.json |
| ホームフォルダを指定している | チェックリスト.checklist.json(WindowsならC:\Users\ユーザー名の直下) |
| 保存のたびに画面で選ぶ | 体重記録・お小遣い帳・習慣トラッカー(チェックリストも選んだ場所に保存) |
1行目の5本はファイル名しか書いていないため、ファイルは.pyの置き場所ではなく、Pythonを起動したときのカレントディレクトリ(作業中のフォルダ)にできます。ターミナルでcdしてから実行したならそのフォルダです。エディタの実行ボタンでは、開いているフォルダが基準になって.pyと別の場所にできることがあります。迷ったらprint(os.path.abspath(self.SAVE_FILE))を1行足すと、実際の保存先が分かります。
どこから起動しても.pyと同じフォルダに保存したいなら、ファイル名の指定を次のように変えます。
from pathlib import Path
SAVE_FILE = Path(__file__).with_name("shopping_list.json")
open()もos.path.exists()もPathを受け取るので、ほかの行はそのままで動きます。この書き方の実例がtkinterのToDoリストです。なお.checklist.jsonのように「.」で始まる名前は、Linuxのファイルマネージャーでは標準で表示されません。
6. JSONとCSVの選び方
10本のうちJSONが6本、CSVが3本です。選び方の目安は、データの形が表に収まるかどうかです。
- JSON:辞書のリストや二重リストをそのまま保存でき、
json.loadした直後から元のデータとして使えます。数値やTrue/Falseも型を保ったまま戻ります - CSV:1件1行の表になるデータ向きで、表計算ソフトで開いて集計できます。代わりに読み込んだ値はすべて文字列になり、お小遣い帳は
int(row["amount"])で数値へ戻しています
気分トラッカーには、保存形式を変えるときの手本になる処理があります。以前のバージョンはJSONで保存していたため、古いmood_log.jsonが残っていればmood_log.csvへ移し替えてから起動します。
def _load(self):
if os.path.exists(self.SAVE_FILE):
with open(self.SAVE_FILE, encoding="utf-8", newline="") as f:
return [dict(row) for row in csv.DictReader(f)]
if os.path.exists(self.LEGACY_FILE):
with open(self.LEGACY_FILE, encoding="utf-8") as f:
self.records = json.load(f)
self._save() # JSON の内容をそのまま CSV に書き出して移行完了
return self.records
return []
CSVを先に探すので、移し替えは最初の1回だけです。自分のアプリの保存形式を後から変えるときも、この順番を真似すれば前のデータを捨てずに済みます。
なお気分トラッカーは1行ずつ追記しているのではなく、"w"モードで見出し行から全件を毎回書き直しています。追記にしたいなら"a"モードで新しい1行だけ書き、見出し行はファイルが新しいときだけ書くよう分岐させます。
文字コードは、お小遣い帳が先頭にBOMという目印を付ける"utf-8-sig"、習慣と気分の2本が目印なしの"utf-8"です。Microsoftのサポート記事「Excel で CSV UTF-8 ファイルを正しく開くには」によると、ダブルクリックでそのまま開けるのはBOM付きのほうです。
7. 保存ファイルが壊れていたときの動き
手でJSONを編集して括弧を消すなど、保存ファイルが壊れると次の起動で読み込みに失敗します。この場面の扱いは3通りに分かれていました。
- エラーで止まる:フラッシュカードは読み込みを
tryで囲んでいないので、flashcards.jsonが壊れていると起動の途中でjson.JSONDecodeErrorが出て処理が止まり、アプリの画面は出ません - 空のデータで起動する:買い物リスト・ペット健康ノート・時間割ビューアーは
except Exceptionで失敗を受け止め、空の状態で起動します。止まらない代わりに、そのまま1件追加(時間割なら1マス編集)すると自動保存が走り、手で直せば救えたはずのファイルが空に近い内容で上書きされます - 中身を確かめてから使う:チェックリストは読み込んだデータがリストかどうかを
isinstanceで確かめ、各項目もtextとdoneだけを取り出して作り直します。時間割ビューアーも、読み込めたデータの行数が6でなければ使いません
自分で作るなら、読み込みに失敗したとき壊れたファイルをos.replace(元の名前, 元の名前 + ".bak")で別名に退避してから空で始める形がおすすめです。止まらないことと、データを失わないことを両立できます。エラーメッセージの読み方はPythonのよくあるエラー一覧にまとめています。
8. 日付の扱い:今日・曜日・期間・同じ日の上書き
日付を保存する5本(お小遣い帳・ペット健康ノート・体重記録・気分・習慣)は、どれも日付を"2026-11-04"のような「年-月-日」の文字列で持っています。月と日を2桁にそろえたこの形なら、文字列のままsorted()しても日付順になります。
入力された日付が正しいかを確かめる
日付を手で入力できる3本は、追加する前に形式をチェックします。お小遣い帳はdate.fromisoformat()、ペット健康ノートと体重記録アプリはdatetime.strptime(文字列, "%Y-%m-%d")で、存在しない日付(2月30日など)はValueErrorになります。
ただし3本とも、チェックが通った後は入力された文字列をそのまま保存します。strptimeは"2026-1-5"のような1桁の月日も受け付けるので、この形で入れると並べ替えの順番が崩れます。チェックに使った日付から.strftime("%Y-%m-%d")で作り直して保存すれば、表記がそろいます。
曜日の出し方は2通り
デイリープランナーは["月","火",…,"日"]のリストを用意し、today.weekday()(月曜が0)を番号にして曜日を取り出します。習慣トラッカーはstrftime("%a")なので表示が実行環境に左右され、下のWindowsの画面では(Sat)のような英語の略称でした。日本語で出したいならリスト方式が確実です。書式記号の一覧はPythonの日付フォーマット早見表にまとめています。
「今日までの7日間」を計算する
習慣トラッカーは起動時にtoday - timedelta(days=6 - i)をi=0〜6で回し、6日前から今日までの日付を作ります。チェック状態は{習慣名: {"2026-11-04": True, …}}という辞書の中の辞書で持ちます。
注意したいのは保存の範囲です。CSVを読み込むと7日より前の記録も辞書に入りますが、画面に出るのも保存するのも今日までの7日分だけです。先週のCSVを読み込んで保存し直すと、7日より前の記録はファイルから消えます。長く記録をためたいなら、辞書にあるすべての日付を書き出すよう改造するのが最初の課題です。
今日しか記録できないアプリと、同じ日の上書き
気分トラッカーには日付の入力欄がなく、date.today()で「押した日」を記録します。同じ日に2回押せば2件残ります。体重記録アプリは逆に、同じ日付で入れると後の値で上書きします。その書き方がfor文のelseです。
for r in self.records:
if r["date"] == date_str:
r["weight"] = weight # 同じ日があれば書き換えて終了
break
else:
# break しなかった=同じ日がなかったときだけ実行される
self.records.append({"date": date_str, "weight": weight})
self.records.sort(key=lambda r: r["date"])
見つかったら更新、見つからなければ追加、を目印の変数なしで書ける形です。
9. 記録を見せる:並べ替えた一覧と折れ線グラフ
表示順と保存順が違うときは、元の位置をtagsに持たせる
お小遣い帳・ペット健康ノート・買い物リストはttk.Treeviewに並べ替えた順で行を出すため、画面の行とリストの位置がずれます。3本ともenumerateで取った元の位置をtagsに持たせ、削除などのときはtree.item(選択行, "tags")[0]で取り出します。
Canvasに折れ線を描く3本
体重記録アプリ・ペット健康ノート・気分トラッカーは、Canvasにcreate_lineとcreate_ovalで折れ線を描きます。体重の2本は、全部同じ値だと高さの計算で0で割ってしまうため、最小と最大を1ずつ広げて避けています。ペット健康ノートは、最初と最新の体重が10%以上違うと赤字で注意を出します。2点を比べるだけの単純な計算で、健康状態を判断できる値ではありません。気になる変化は獣医師に相談してください。
気をつけたいのは起動直後のCanvasの大きさです。体重記録アプリは起動時にwinfo_width()で幅を読んで描きますが、ウィンドウが表示される前は正しい幅がまだ入っていません。下の画面でも「記録がありません」の文字が中央ではなく左上で切れています。
気分トラッカーはこの問題をcanvas.bind("<Configure>", …)で避けています。大きさが決まったときや変わったときに届くイベントで、そのたびに描き直すため、§6の画面では同じ文字が枠の中央に出ています。もう1つ、体重記録アプリの横軸は「何件目か」の等間隔なので、1週間休んでも次の点は前の点の隣に来ます。日付の間隔を反映したいなら、横位置を(日付 - 最初の日付).daysで計算します。
10. よくある質問(FAQ)
Q. 保存したはずのファイルが見つかりません。
ファイル名だけで保存している5本は、.pyの場所ではなくPythonを起動したときのカレントディレクトリにファイルを作ります。確かめ方と、.pyの隣に固定する書き方は§5にまとめました。
Q. tkinterアプリのデータはJSONとCSVのどちらで保存すればいいですか?
1件1行の表にできて後で表計算ソフトで集計したいならCSV、入れ子のデータや数値・真偽値をそのまま戻したいならJSONです。迷ったら、json.dumpとjson.loadの2行で済むJSONから始めてください。CSVは読み込んだ値が文字列になるので、数値はint()などで戻します。
Q. 過去の日付で記録をつけられますか?
アプリによります。お小遣い帳・ペット健康ノート・体重記録アプリは日付の入力欄があり、過去の日付も入れられます。習慣トラッカーは今日までの7日間ならチェックを付けられます。気分トラッカーは記録した日の日付が自動で入るので、過去の日付では記録できません。
本記事は、生成AIを活用して下書きし、運営者が内容を確認・編集したうえで公開しています。AIの利用方針は免責事項をご覧ください。
11. まとめ:どれから作るか
- 最初の1本は買い物リスト。起動時に読み、変更のたびに書く自動保存の基本形がそのまま入っている
- デイリープランナーは保存しない作り。
_load()と_save()を足す改造の練習台になる
作るものが決まったら、表の.pyを動かして1つだけ改造してみてください。フラッシュカードに削除ボタンを足す、体重記録アプリに起動時の自動読み込みを足す、習慣トラッカーに全期間を保存させる、のどれもこの記事で見た仕組みで書けます。
収入と支出を分けて管理したいなら家計簿アプリの記事、ファイルを扱う処理をもう一歩進めるならフォルダ整理提案が次の題材です。データが増えてきたらtkinter×SQLiteの業務アプリが次の一歩です。記録より遊びの題材がよければtkinterミニゲーム20選もどうぞ。