つくる:関数に分割
このレッスンで作るもの — 第 6 章の総仕上げとして、件数と合計と残額と予算オーバー判定をまとめて返す
summarizeを作ります。
つくる — 関数に分割
第 1 章から第 5 章までに、おこづかい帳の部品はもう出そろっています。
make_receipt はレシートの 1 行を作り、check_budget は予算と比べて文にし、guess_category は品名からカテゴリを決め、show_all は記録を一覧にします。この章で学んだのは、それらを 1 つのファイルの中で並べて使う ための道具でした。
def make_receipt(name, price):
return name + " " + str(price) + "円"
def check_budget(total, budget):
...
def guess_category(name):
if "りんご" in name or "パン" in name or "牛乳" in name:
return "食費"
return "その他"
def show_all(records):
...
def add_record(records, name, price, category):
return records + [{"name": name, "price": price, "category": category}]
def summarize(records, budget):
...kakeibo.py はこういう姿になります。章ごとに 1 つずつ増やしてきた関数が、いま 1 つのファイルに縦に並んでいます。上から順に定義が並び、いちばん下で組み合わせて使う。ここまで来ると、もうアプリの形です。
どの関数も、外の変数をこっそり書き換えていません。入口は引数、出口は戻り値で揃っているので、順番を入れ替えても他の関数に影響が出ません。
1 つの関数が短く、名前を見れば役割が分かる。この状態を保てているうちは、機能をいくつ足しても手に負えなくなりません。
何を返す関数にするか
この章の仕上げで作るのは summarize です。記録の一覧と予算を受け取り、知りたい数字をまとめて返します。
返すのは文字列ではなく 辞書 にします。理由は、受け取った側が好きな形に組み立て直せるからです。文字列で返すと、画面の表示を変えたいときに関数の中まで直しに行くことになります。
{"count": 3, "total": 2240, "remaining": 760, "over": False}キーは 4 つで固定です。count が件数、total が合計金額、remaining が残額、over が予算オーバーかどうかの True か False です。
辞書そのものは第 7 章で詳しく扱いますが、{キー: 値} の形で作り、結果["total"] のように取り出す、という 2 つだけ分かっていれば今日は足ります。
組み立て方
合計は第 5 章の for で足し上げます。件数は len で一発です。
def summarize(records, budget):
total = 0
for record in records:
total = total + record["price"]
return {
"count": len(records),
"total": total,
"remaining": budget - total,
"over": total > budget,
}remaining は budget - total です。使いすぎたときは負の数になります。-500 という値がそのまま「500 円の使いすぎ」を表すので、これで困りません。
over は total > budget の結果をそのまま入れます。ちょうど予算どおりのときは False です。これは check_over で決めた仕様と同じで、章をまたいで判定がぶれないようにしています。
数字を返す関数と、文にする関数を分けておきます。分けておけば、表示を変えたいときに計算のコードを触らずに済みます。
動きを追ってみる
記録が 3 件、予算が 3000 のときの流れは下記のとおりです。
totalを0から始める- 1 件目の
priceの120を足して120になる - 2 件目の
1800を足して1920になる - 3 件目の
320を足して2240になる countはlen(records)で3、remainingは3000 - 2240で760overは2240 > 3000なのでFalse- 4 つのキーを持つ辞書が返る
記録が 0 件のときは for が 1 度も回らず、total は 0 のままです。count は 0、remaining は予算そのもの、over は False になります。空のときも自然に正しい答えが出る 形になっているかを、必ず確かめます。
よくある間違い
- キーの名前や数を変える — キーは
counttotalremainingoverの 4 つです。1 つでも綴りが違うと、受け取る側が取り出せません。 totalの初期化を忘れる —forの前にtotal = 0を書かないとNameErrorです。合計を作るときの定番の手順です。overを文字列にする —"オーバー"のような文字列ではなく、TrueかFalseの真偽値を入れます。判定結果は真偽値で返し、言葉にするのは表示側の仕事です。
やってみよう
関数 summarize(records, budget) を実装してください。支出のリスト records と予算 budget を受け取り、下記の 4 つのキーを持つ辞書を return します。
count は件数、total は金額の合計、remaining は budget - total、over は合計が予算を超えていれば True、そうでなければ False です。records が空のリストのときは、count と total が 0、remaining が budget の値、over が False になります。
要件
- 関数名は summarize で、引数は records と budget の 2 つ
- 戻り値は count / total / remaining / over の 4 つのキーを持つ辞書
- remaining は budget - total で、負の数になってよい
- over は total > budget の結果の True か False にする
入出力例
summarize([{"category":"食費","name":"りんご","price":120},{"category":"娯楽","name":"映画","price":1800},{"category":"交通費","name":"電車","price":320}], 3000) → {"count":3,"over":false,"remaining":760,"total":2240}
summarize([], 3000) → {"count":0,"over":false,"remaining":3000,"total":0}
summarize([{"category":"娯楽","name":"ゲーム","price":4800},{"category":"食費","name":"パン","price":250}], 3000) → {"count":2,"over":true,"remaining":-2050,"total":5050}
summarize([{"category":"交通費","name":"定期券","price":3000}], 3000) → {"count":1,"over":false,"remaining":0,"total":3000}
summarize([{"category":"その他","name":"おまけ","price":0}], 500) → {"count":1,"over":false,"remaining":500,"total":0}