全機能を統合しよう

このレッスンで作るもの — 第 1 章から第 8 章までに作ってきた部品を 1 本のアプリにまとめます。コマンドを順に処理して最後の状態を返す run_kakeibo が、おこづかい帳の本体になります。

全機能を統合しよう

ここからは総合制作です。新しい文法はもう出てきません。これまでに書いた関数をつなぎ直して、ひとつのアプリとして動かします。

ここまでに作ってきた部品

各章の「つくる回」で、下記の 8 つの関数ができています。

  1. make_receipt — 品名と金額をレシートの 1 行に整える (第 1 章、第 2 章)
  2. check_budget — 合計と予算を比べて警告つきの文字列を返す (第 3 章)
  3. guess_category — 品名からカテゴリを推測する (第 4 章)
  4. show_all — 記録をまとめて一覧の文字列にする (第 5 章)
  5. summarize — 件数や合計をまとめた辞書を返す (第 6 章)
  6. total_by_category — カテゴリ別の合計を辞書で返す (第 7 章)
  7. save_and_load — ファイルに保存して読み戻す (第 8 章)

どれも「入力を受け取って値を返す」形になっています。だからこそ、上に 1 枚かぶせるだけでアプリになります。その 1 枚が今回の run_kakeibo です。

部品が正しく分かれていれば、統合は「並べ替え」で済みます。逆に、すべてを 1 つの関数に書いてしまうと、この段階で手が止まります。関数に分けてきたのはこの日のためです。

diagram (will load when visible)

本物のメニューは while と input で回す

手元の kakeibo.py では、下記のように while True の無限ループの中で input() を呼び、打ち込まれた番号で処理を振り分けます。

def main(): records = [] while True: print("1 追加 / 2 削除 / 3 集計 / 4 終了") choice = input("番号を選んでください > ") if choice == "1": name = input("品名 > ") price = int(input("金額 > ")) records.append({"name": name, "price": price, "category": guess_category(name)}) elif choice == "3": print(total_by_category(records)) elif choice == "4": break

while True は「条件が常に真」なので、break に当たるまで永久に回り続けます。4 を選んだときだけ break が実行され、ループから抜けて関数が終わります。

採点では input が使えない

このコースの採点環境では input() が使えません。キーボードがつながっていないので、呼んでも常に空文字が返ってきます。そこで演習では、打ち込まれるはずだった操作をあらかじめリストにして引数で渡す形にします。

やっていることは同じです。input() から 1 行ずつ受け取る代わりに、リストから 1 つずつ取り出すだけの違いです。ループの中身は一切変わりません。

外から来る値を引数で受け取る形にしておくと、テストが書けるようになります。実務でも「入力を読む部分」と「処理する部分」は分けるのが定石です。

コマンドの形

コマンドは「操作名 + 必要な値」のリストです。3 種類だけ使います。

["add", "りんご", 120, "食費"] ["delete", "りんご"] ["summary"]

add は 1 件追加、delete は品名が一致する記録を 1 件だけ消す、summary は集計を画面に見せる操作です。summary は表示するだけなので記録そのものは変わりません。知らない操作名が来たときは、落とさずに無視します。

戻り値は最後の状態をまとめた辞書で、キーは recordscounttotal の 3 つに固定します。

{"records": [{"name": "りんご", "price": 120, "category": "食費"}], "count": 1, "total": 120}

動きを追ってみる

run_kakeibo([["add", "りんご", 120, "食費"], ["add", "バス", 220, "交通費"], ["summary"]]) を追いかけます。

まず records が空のリストで始まります。1 つめのコマンドは add なので、辞書 {"name": "りんご", "price": 120, "category": "食費"}append します。2 つめも add なので、バスの記録が加わって 2 件になります。3 つめの summary は表示だけなので記録は 2 件のままです。

ループを抜けたら合計を出します。120 + 220340 です。最後に {"records": [りんごの辞書, バスの辞書], "count": 2, "total": 340} を返します。

カテゴリが空文字 "" で来たときは "その他" に置き換えます。第 4 章の fill_category と同じ考え方で、未入力を既定値で埋めます。

よくある間違い

  1. command[0] 以外を先に取り出してしまう["summary"] は要素が 1 つしかないので、いきなり command[1] を読むと IndexError になります。操作名で分岐したあと、その枝の中で取り出します。
  2. 削除で全部消してしまう — 同じ品名が 2 件あるとき、remove を繰り返すと両方消えます。1 件だけ消すなら break でループを抜けます。
  3. 合計をループの中で数え直すadd のたびに合計を作り直すと、delete のあとにずれます。最後にまとめて足すほうが確実です。

count を自分で数える必要はありません。len(records) がそのまま件数です。

やってみよう

関数 run_kakeibo(commands) を実装してください。commands はコマンドのリストです。

add で記録を追加し、delete で品名が一致する記録を 1 件だけ消し、summary と知らない操作は何もしません。カテゴリが空文字のときは "その他" にします。最後に {"records": 記録のリスト, "count": 件数, "total": 合計金額} の辞書を返します。空のコマンド列が来たら、records は空のリスト、counttotal0 です。

要件

  1. 関数名は run_kakeibo で、引数はコマンドのリスト commands 1つだけ
  2. add は {"name": 品名, "price": 金額, "category": カテゴリ} を1件追加する
  3. delete は品名が一致する記録を1件だけ消す。該当がなければ何もしない
  4. summary と知らない操作名は何もしない。エラーにもしない
  5. カテゴリが空文字のときは その他 に置き換える
  6. 戻り値は records と count と total の3つのキーを持つ辞書にする

入出力例

run_kakeibo([]){"count":0,"records":[],"total":0} run_kakeibo([["add","りんご",120,"食費"],["add","バス",220,"交通費"],["summary"]]){"count":2,"records":[{"category":"食費","name":"りんご","price":120},{"category":"交通費","name":"バス","price":220}],"total":340} run_kakeibo([["add","ノート",200,"日用品"],["add","ペン",150,"日用品"],["add","ノート",300,"日用品"],["delete","ノート"]]){"count":2,"records":[{"category":"日用品","name":"ペン","price":150},{"category":"日用品","name":"ノート","price":300}],"total":450} run_kakeibo([["add","なぞの出費",500,""]]){"count":1,"records":[{"category":"その他","name":"なぞの出費","price":500}],"total":500} run_kakeibo([["add","映画",1800,"娯楽"],["undo"],["summary"]]){"count":1,"records":[{"category":"娯楽","name":"映画","price":1800}],"total":1800} run_kakeibo([["add","パン",180,"食費"],["delete","うどん"]]){"count":1,"records":[{"category":"食費","name":"パン","price":180}],"total":180}

ヒント

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

メモ

全機能を統合しよう

⌘S で保存