Pythonのグローバルスコープについての注意点

Pythonのグローバルスコープについてハマった点を紹介します。

## ハマった点
以下のコードを実行すると「1」が表示されることを期待したのですが、実際には `UnboundLocalError: local variable 'score' referenced before assignment` というエラーが表示されます。

```python:score.py
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 関数内で変更できるようになります。

```python:score.py
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()`で確認することができます。
実際にコードに埋め込んでみたいと思います。

```python:score.py
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))
```

実行結果は以下の通りです。

```bash:実行結果
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`定義するとどうなるかを確認してみます。

```python:score.py
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))
```

実行結果は以下の通りです。

```bash:実行結果
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 を扱えることが確認できると思います。

以上です。