Pythonのグローバルスコープについてハマった点を紹介します。
ハマった点
以下のコードを実行すると「1」が表示されることを期待したのですが、実際には UnboundLocalError: local variable 'score' referenced before assignment
というエラーが表示されます。
score = 0 # グローバルスコープ
def my_func(point):
score += point
return score
if __name__ == "__main__":
print(my_func(1))
エラーの内容としては「ローカル変数 score を初期化せずに使わっていますよ」という意味なのですが、グローバルスコープの score に引数の point を加算したつもりだったので、一瞬このエラーメッセージの意味が分かりませんでした。
調べてみるとわかったのですが、Pythonには「関数内で変数の値を変更する場合、ローカルスコープが適用される」という注意点があるそうです。
具体的に言うと、「my_func 関数の中で score に point を加算しようとすると、my_func関数内のローカルスコープ内に score という別の変数が作られて、そちら(ローカルスコープ)の変数が使われる」ということになります。
結果的に、「グローバルスコープ score を参照していたつもりが、my_func 関数内のローカルスコープ score が適応されており、ローカルスコープ score を初期化せずに point を加算していたため、上記のエラーが出てしまった」ということが起きていたようです。
解決策
global
で定義することでグローバルスコープの score を my_func 関数内で変更できるようになります。
score = 0
def my_func(point):
global score
score += point # グローバルスコープ score に加算
return score
if __name__ == "__main__":
print(my_func(1)) # 1
globals()とlocals()
グローバルスコープやローカルスコープは、globals()
やlocals()
で確認することができます。
実際にコードに埋め込んでみたいと思います。
score = 0
print("check1")
print(globals())
def my_func(point):
score = 99 # ローカルスコープの score が作成される
print("check2")
print(globals())
print("check2")
print(locals())
score += point
return score
if __name__ == "__main__":
print(my_func(1))
実行結果は以下の通りです。
python scope.py
check1
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x01E1DB70>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'scope.py', '__cached__': None, 'score': 0}
check2
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x01E1DB70>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'scope.py', '__cached__': None, 'score': 0, 'my_func': <function my_func at 0x01DF18E8>}
check3
{'point': 1, 'score': 99}
100
注目すべきは、check1~check3の{'score'}
の値です。
check1の「0」はグローバルスコープ score の値です。(=0で初期化している)
check2の「0」はグローバルスコープ score の値です。(=99されたのはローカルスコープの方)
check3の「99」は、score = 99
をしたことにより作成されてしまった、ローカルスコープ score の値です。
確かに、ローカルスコープ score が作成されたことが確認できるかと思います。
次に、global
定義するとどうなるかを確認してみます。
score = 0
print("check1")
print(globals())
def my_func(point):
global score
print("check2")
print(globals())
print("check3")
print(locals())
score += point
return score
if __name__ == "__main__":
print(my_func(1))
実行結果は以下の通りです。
check1
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x01B2DB70>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'scope.py', '__cached__': None, 'score': 0}
check2
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x01B2DB70>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'scope.py', '__cached__': None, 'score': 0, 'my_func': <function my_func at 0x01B018E8>}
check3
{'point': 1}
1
check1の「0」はグローバルスコープ score の値です。(=0で初期化している)
check2の「0」はグローバルスコープ score の値です。
check3には、ローカルスコープ score が登場しません。なぜなら score = 99
を削除したことにより、ローカルスコープ score が作成されなくなったためです。
global
定義をすると、きちんとグローバルスコープ score を扱えることが確認できると思います。
以上です。