変数のスコープ

このレッスンで作るもの — 関数の外の変数が書き換えられない理由を知り、記録を 1 件足した新しいリストを返す add_record を作ります。

変数のスコープ

変数には「どこから見えるか」という範囲があります。これを スコープ と呼びます。関数の中で作った変数は関数の中だけで生き、関数が終わると消えます。

def add_tax(price): tax = price // 10 return price + tax print(tax)

最後の行は NameError です。taxadd_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 は、右辺で使おうとした時点ではまだ何も入っていません。だからエラーになります。

diagram (will load when visible)

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, "食費") の流れは下記のとおりです。

  1. records に空のリスト、name"りんご"price120category"食費" が入る
  2. new_record{"name": "りんご", "price": 120, "category": "食費"} が作られる
  3. [] + [new_record] で、要素が 1 つの新しいリストができる
  4. その新しいリストが返る

すでに 2 件入っているリストを渡せば、3 件のリストが返ります。渡したリスト自体は 2 件のままです。

よくある間違い

  1. 関数の外の変数を直接書き換えようとするUnboundLocalError になります。global で押し切らず、引数と戻り値の形に直します。
  2. 戻り値を受け取り忘れるadd_record(records, "りんご", 120, "食費") とだけ書いて records = を付けないと、新しいリストは捨てられ、何も増えません。エラーが出ないぶん気づきにくい間違いです。
  3. キーの名前を間違える — キーは namepricecategory の 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 件に増えることを確かめてみてください。

要件

  1. 関数名は add_record で、引数は records, name, price, category の 4 つ
  2. 1 件は {"name": ..., "price": ..., "category": ...} の形の辞書にする
  3. 新しい記録はリストの末尾に足す
  4. 元の 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}]

ヒント

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

メモ

変数のスコープ

⌘S で保存