変数のスコープ
このレッスンで作るもの — 関数の外の変数が書き換えられない理由を知り、記録を 1 件足した新しいリストを返す
add_recordを作ります。
変数のスコープ
変数には「どこから見えるか」という範囲があります。これを スコープ と呼びます。関数の中で作った変数は関数の中だけで生き、関数が終わると消えます。
def add_tax(price):
tax = price // 10
return price + tax
print(tax)最後の行は NameError です。tax は add_tax の中で作られた ローカル変数 で、外からは見えません。関数ごとに使い捨ての作業台がある、と考えると分かりやすいです。
関数の中の名前が外に漏れないのは不便ではなく、むしろ利点です。別の関数で同じ
totalという名前を使っても、ぶつかりません。
外の変数は読めるが書き換えられない
関数の外で作った変数は グローバル変数 と呼ばれ、関数の中から読むことはできます。ところが書き換えようとすると、話が変わります。
records = []
def add_record(name, price):
records = records + [{"name": name, "price": price}]
return recordsこれを実行すると UnboundLocalError になります。cannot access local variable 'records' where it is not associated with a value というメッセージです。
理由は、Python が関数の中を先に読んで「この関数の中に records への代入がある」と判断し、records をローカル変数だと決めてしまうためです。ローカル変数として扱われた records は、右辺で使おうとした時点ではまだ何も入っていません。だからエラーになります。
global を使わずに直す
global records と書けばエラーは消えます。しかしこの書き方はおすすめしません。関数の外の状態が、関数を呼ぶたびにこっそり変わるからです。どこで何が変わったのかを追えなくなり、規模が大きくなるほど手に負えなくなります。
もっとよい直し方は、必要なものを引数で受け取り、結果を戻り値で返す ことです。
def add_record(records, name, price, category):
new_record = {"name": name, "price": price, "category": category}
return records + [new_record]records + [new_record] は、元のリストと 1 件だけのリストをつないだ 新しいリスト を作ります。元の records はそのままです。呼び出す側は下記のように書きます。
records = []
records = add_record(records, "りんご", 120, "食費")外の変数が変わるのは、この records = ... の行だけです。どこで変わったかが 1 行で見えます。
ここで作っている {"name": ..., "price": ..., "category": ...} は辞書です。第 5 章の一覧表示でも使いましたが、辞書そのものは第 7 章で詳しく扱います。今は「1 件ぶんの情報をまとめた入れ物」と考えれば十分です。
関数の外を書き換える関数は、書いた本人にしか動きが読めません。入口は引数、出口は戻り値。この形を守るだけで、あとから読める関数になります。
動きを追ってみる
add_record([], "りんご", 120, "食費") の流れは下記のとおりです。
recordsに空のリスト、nameに"りんご"、priceに120、categoryに"食費"が入るnew_recordに{"name": "りんご", "price": 120, "category": "食費"}が作られる[] + [new_record]で、要素が 1 つの新しいリストができる- その新しいリストが返る
すでに 2 件入っているリストを渡せば、3 件のリストが返ります。渡したリスト自体は 2 件のままです。
よくある間違い
- 関数の外の変数を直接書き換えようとする —
UnboundLocalErrorになります。globalで押し切らず、引数と戻り値の形に直します。 - 戻り値を受け取り忘れる —
add_record(records, "りんご", 120, "食費")とだけ書いてrecords =を付けないと、新しいリストは捨てられ、何も増えません。エラーが出ないぶん気づきにくい間違いです。 - キーの名前を間違える — キーは
nameとpriceとcategoryの 3 つです。namaeなどと書くと、あとで取り出すときに落ちます。
やってみよう
関数 add_record(records, name, price, category) を実装してください。支出のリスト records に、{"name": name, "price": price, "category": category} という 1 件を足した 新しいリスト を return します。
元の records は書き換えません。records + [new_record] の形で新しいリストを作ります。手元では records = add_record(records, "りんご", 120, "食費") を 2 回続けて実行し、件数が 2 件に増えることを確かめてみてください。
要件
- 関数名は add_record で、引数は records, name, price, category の 4 つ
- 1 件は {"name": ..., "price": ..., "category": ...} の形の辞書にする
- 新しい記録はリストの末尾に足す
- 元の records を書き換えず、新しいリストを返す
入出力例
add_record([], "りんご", 120, "食費") → [{"category":"食費","name":"りんご","price":120}]
add_record([{"category":"食費","name":"りんご","price":120}], "電車", 320, "交通費") → [{"category":"食費","name":"りんご","price":120},{"category":"交通費","name":"電車","price":320}]
add_record([], "おまけ", 0, "その他") → [{"category":"その他","name":"おまけ","price":0}]
add_record([{"category":"食費","name":"パン","price":250}], "パン", 250, "食費") → [{"category":"食費","name":"パン","price":250},{"category":"食費","name":"パン","price":250}]
add_record([{"category":"食費","name":"りんご","price":120},{"category":"交通費","name":"電車","price":320}], "映画", 1800, "娯楽") → [{"category":"食費","name":"りんご","price":120},{"category":"交通費","name":"電車","price":320},{"category":"娯楽","name":"映画","price":1800}]