自由拡張

このレッスンで作るもの — 品名の一部から支出を探し出す search_records を作ります。そのあと、自分ならこのアプリに何を足すかを考える回です。

自由拡張

記録して、集計して、レポートまで出せるようになりました。ここまで来ると、使っているうちに「これができたらいいのに」が出てきます。今回はその第一歩として検索を足し、そのあと拡張の考え方を扱います。

検索が欲しくなる理由

記録が 100 件になると、一覧を目で追うのがつらくなります。「先月コーヒーにいくら使ったか」を知りたいだけなのに、全部を眺める羽目になります。

欲しいのは「品名に コーヒー を含む記録だけ」を取り出す機能です。完全一致ではなく 部分一致 にするのが肝で、アイスコーヒーコーヒー豆 も一緒に拾えます。

検索は、絞り込みの道具であると同時に、記録が増えても壊れないことの証明でもあります。件数が増えるほど価値が出る機能です。

in で部分一致を調べる

第 3 章で in を「リストに含まれるか」の判定として使いました。同じ in は、文字列に対しても使えます。

"コーヒー" in "アイスコーヒー"

これは True になります。左が右のどこかに現れれば真、という意味です。左右の順番を逆にすると別の意味になるので、キーワード in 品名 の向きを守ります。

あとは第 5 章の for と第 7 章の append を組み合わせるだけです。

def search_records(records, keyword): found = [] for record in records: if keyword in record["name"]: found.append(record) return found

第 7 章のリスト内包表記で書くと 1 行になります。どちらでも構いません。

def search_records(records, keyword): return [record for record in records if keyword in record["name"]]
diagram (will load when visible)

見つからなかったとき

該当がゼロなら、空のリスト [] を返します。None を返してはいけません。呼び出す側が len()for をそのまま使えるほうが扱いやすいからです。

search_records([], "コーヒー")

記録が空なら当然 [] です。空のリストは if found: が偽になるので、呼び出し側は「見つかりませんでした」の表示に素直に分岐できます。

もうひとつ、キーワードが空文字 "" のときは全件が返ります。"" in "りんご"True になるためです。これは Python の仕様で、直したいなら呼び出す前に空かどうかを確かめます。仕様として受け入れるなら、そのままで構いません。

空の結果を None で返す設計は、呼び出し側に if 結果 is None の分岐を強制します。同じ「無い」を表すなら、型を変えない [] のほうが後々楽です。

動きを追ってみる

記録が アイスコーヒー 480円 娯楽りんご 120円 食費コーヒー豆 900円 食費 の 3 件で、キーワードが コーヒー の場合を追います。

1 件目の品名は アイスコーヒー で、コーヒー を含むので found に入ります。2 件目の りんご は含まないので飛ばします。3 件目の コーヒー豆 は含むので入ります。結果は 1 件目と 3 件目の 2 件です。

このとき返っているのは記録そのもの、つまり辞書です。金額もカテゴリも付いたままなので、monthly_report にそのまま渡せます。「コーヒーだけの月間レポート」が、新しいコードなしで出せることになります。

次に何を足すか

検索ができたら、次は自分で決める番です。考えられる拡張を挙げておきます。

  1. 日付を持たせる — 記録に "date" を足して {"name": "りんご", "price": 120, "category": "食費", "date": "2026-08-05"} にします。並べ替えも期間の絞り込みもできるようになります。
  2. 月をまたいで集計する — 日付があれば、月ごとにまとめられます。monthly_report を月の指定つきに拡張し、先月との差を出すところまで進めます。
  3. グラフにする — カテゴリ別の合計を * の数で表すだけでも棒グラフになります。"食費" + " " + "*" * (300 // 100) のような組み立てで、追加のライブラリなしに作れます。
  4. CSV にして Excel で開く — 第 8 章の save_and_load品名,金額,カテゴリ の行に変えれば、そのまま表計算ソフトで開けます。手元の家計簿として本当に使えるようになります。

どれも、これまでに習った文法だけで作れます。新しい知識を足す前に、持っている道具で作れる範囲を広げるほうが力が付きます。

よくある間違い

  1. in の向きを逆にするrecord["name"] in keyword と書くと、品名がキーワードに含まれるかという逆の判定になります。ほとんどの場合ずっと偽になります。
  2. 見つからないときに None を返す — 呼び出し側が for で回した瞬間に TypeError になります。空のリストを返します。
  3. 元のリストから消しながら探すremove で絞り込もうとすると、元の記録が壊れます。新しいリストに集めるのが安全です。

やってみよう

関数 search_records(records, keyword) を実装してください。records は支出の辞書のリスト、keyword は探したい文字列です。

品名に keyword を含む記録だけを、元の順番のまま新しいリストにして返します。1 件も見つからないときと、records が空のときは、どちらも空のリストを返してください。返すのは辞書そのもので、品名だけを取り出したりはしません。

要件

  1. 関数名は search_records で、引数は records と keyword の2つ
  2. 品名に keyword を含む記録だけを集める。完全一致ではなく部分一致にする
  3. 返すのは記録の辞書そのもの。品名だけを取り出さない
  4. 元の並び順を変えない
  5. 1件も見つからないときと records が空のときは、空のリストを返す

入出力例

search_records([{"category":"娯楽","name":"アイスコーヒー","price":480},{"category":"食費","name":"りんご","price":120},{"category":"食費","name":"コーヒー豆","price":900}], "コーヒー")[{"category":"娯楽","name":"アイスコーヒー","price":480},{"category":"食費","name":"コーヒー豆","price":900}] search_records([{"category":"食費","name":"りんご","price":120},{"category":"交通費","name":"バス","price":220}], "うどん")[] search_records([], "コーヒー")[] search_records([{"category":"食費","name":"りんご","price":120},{"category":"交通費","name":"バス","price":220}], "")[{"category":"食費","name":"りんご","price":120},{"category":"交通費","name":"バス","price":220}] search_records([{"category":"日用品","name":"ノート","price":200},{"category":"日用品","name":"ペン","price":150}], "ノート")[{"category":"日用品","name":"ノート","price":200}] search_records([{"category":"食費","name":"パン","price":180},{"category":"食費","name":"食パン","price":250},{"category":"食費","name":"牛乳","price":240},{"category":"食費","name":"菓子パン","price":130}], "パン")[{"category":"食費","name":"パン","price":180},{"category":"食費","name":"食パン","price":250},{"category":"食費","name":"菓子パン","price":130}]

ヒント

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

メモ

自由拡張

⌘S で保存