1. はじめに
変数のスコープをあまり理解していなかったせいでPythonに怒られた。
まず、読み込みの重い処理で、初回のみ読み込むようにしたくて書いたのが以下のようなコードだった。
menu = None
def get_menu():
if menu is not None:
return menu
menu = <メニューを読み込む重い処理がここに入る>
return menu
しかし、get_menu()を呼び出すと
>>> get_menu()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in get_menu
UnboundLocalError: local variable 'menu' referenced before assignment
というエラーが返ってきた。menu
はグローバル変数なはずなのにローカル変数として扱われている。何故...
2. global宣言
大前提として、関数内で単純に<変数名> = <代入内容>
という形でグローバル変数に代入することはできず、global宣言が必要になるらしい。
var = 0
def func_1():
print(var)
def func_2():
var = 2
print(var)
def func_3():
global var # グローバル宣言
var = 3
print(var)
上記のように3種類の関数を定義し、挙動を見てみると
>>> var
0
>>> func_1()
0 # ここで出力されているvarはグローバル変数
>>> func_2()
2 # ここで出力されているvarはfunc_2内のローカル変数
>>> var
0 # グローバル変数のvarはfunc_2によって変更されていない
>>> func_3()
3 # ここで出力されているvarはグローバル変数
>>> var
3 # グローバル変数のvarがfunc_3によって変更された
つまり、
-
func_1
のように関数内からグローバル変数は当然参照できる - しかし、
func_2
のようにglobal宣言なしに代入しようとすると、var
はグローバル変数とは異なる、新たに定義されたローカル変数となる -
func_3
のようにglobal宣言を行うと、関数内でもグローバル変数に代入できる
ということになる。
最初のget_menu()
関数だと、
menu = <メニューを読み込む重い処理がここに入る>
でglobal宣言なしにmenu
に代入しているため、このmenu
はグローバル変数ではなく、新たに定義されたローカル変数menu
ということになる。
しかし、
if menu is not None:
のmenu
はローカル変数menu
への代入前に参照しているので、グローバル変数なのでは?という疑問が生じた。そこで、公式ドキュメントを訪ねると
In Python, variables that are only referenced inside a function are implicitly global. If a variable is assigned a value anywhere within the function’s body, it’s assumed to be a local unless explicitly declared as global.(Python では、関数内で参照されるだけの変数は暗黙的にグローバルとなります。 関数の本体のどこかで値が変数に代入されたなら、それは明示的にグローバルであると宣言されない限り、ローカルであるとみなされます。)(順に公式ドキュメント英語版・日本語版)
と書いてあった。関数内のどこかにグローバル変数と同名のローカル変数への代入を行っている箇所があれば、たとえ代入前の行であったとしてもその名前の変数はローカル変数とみなされるらしい。
3. 解決策
menu = None
def get_menu():
global menu
if menu is not None:
return menu
menu = <メニューを読み込む重い処理がここに入る>
return menu
のようにglobal宣言を追加すればよい。
>>> get_menu() # 初回実行時のみメニューを読み込むので時間がかかる
"メニューの内容"
>>> get_menu() # 2回目以降はすぐに実行完了する
"メニューの内容"
という期待通りの動作をしてくれる。
4. まとめ
global宣言を行うとその変数をグローバル変数として解釈させ、関数内で代入させることができる。
一方、nonlocal宣言というものもあり、こちらは一つ外側のスコープの変数として解釈する宣言らしい。
nonlocal 文は、列挙された識別子がグローバルを除く一つ外側のスコープで先に束縛された変数を参照するようにします。(公式ドキュメント)
global宣言とnonlocal宣言の挙動については、次の記事がわかりやすかったので、globalとnonlocalの動きを具体例で知りたい!という方は是非。
また、スコープの基礎的な話は次の記事が勉強になりました。
5. 参考
Programming FAQ — Python 3.11.4 documentation
プログラミング FAQ — Python 3.11.4 ドキュメント
7. 単純文 (simple statement) — Python 3.11.4 ドキュメント