変数のスコープ
関数の外の変数を書き換えようとすると落ちる
変数には「どこから見えるか」という範囲があります。これを スコープ と呼びます。関数の中で作った変数は関数の中だけで生き、関数が終わると消えます。関数ごとに使い捨ての作業台がある、と考えると分かりやすいです。
外で作った変数を関数の中から読むことはできます。ところが書き換えようとすると、話が変わります。
Python
count = 0
def add_one():
count = count + 1
return count
print(add_one())UnboundLocalError になります。cannot access local variable 'count' where it is not associated with a value というメッセージです。
理由は、Python が関数の中を先に読み、「この関数の中に count への代入がある」と判断して、count をこの関数の変数だと決めてしまうためです。関数の中の変数として扱われた count には、右辺で使おうとした時点でまだ何も入っていません。
global で押し切らない
先頭に global count と書けばエラーは消えます。しかしこの書き方はおすすめしません。関数の外の状態が、関数を呼ぶたびにこっそり変わるからです。どこで何が変わったのかを追えなくなり、規模が大きくなるほど手に負えなくなります。
もっとよい直し方は、必要なものを引数で受け取り、結果を戻り値で返す ことです。
Python
def add_one(count):
return count + 1
count = 0
count = add_one(count)
count = add_one(count)
print(count)2 が出ます。外の変数が変わるのは count = ... の行だけなので、どこで変わったかが 1 行で見えます。関数のほうは、外に何があるかを一切知らずに済みます。
関数の外を書き換える関数は、書いた本人にしか動きが読めません。入口は引数、出口は戻り値。この形を守るだけで、あとから読める関数になります。
元を壊さずに 1 件足す
リストでも考え方は同じです。リストどうしは + でつなげます。
Python
print([1, 2] + [3])[1, 2, 3] が出ます。できるのは 新しいリスト で、左の [1, 2] は変わりません。渡されたリストをそのままにして、1 件足したものを返す形が、こうして書けます。
戻り値を受け取り忘れると、新しいリストは捨てられて何も増えません。エラーが出ないぶん、いちばん気づきにくい間違いです。呼び出しの行に = があるかを必ず見ます。
やってみよう
関数 add_record(records, name, price, category) を実装してください。支出のリスト records に、{"name": name, "price": price, "category": category} という 1 件を足した 新しいリスト を return します。
元の records は書き換えません。キーは name と price と category の 3 つで、綴りを変えると受け取る側が取り出せなくなります。
手元では records = add_record(records, "りんご", 120, "食費") を 2 回続けて実行し、件数が 2 件に増えることを確かめてみてください。
要件
- 関数名は add_record で、引数は records, name, price, category の 4 つ
- 1 件は {"name": ..., "price": ..., "category": ...} の形の辞書にする
- 新しい記録はリストの末尾に足す
- 元の records を書き換えず、新しいリストを返す