【Python】【スコープ】globalとnonlocalとは？！

　僕がPythonを勉強し始めて最初に「何だこれ?」と感じたのが「global」と「nonlocal」でした。

　簡単に説明するなら、どちらも「同じ変数を関数の中でも外でも使いたい時」に用いるものです。そして関数が入れ子構造で定義されているときに「nonlocal」が使われるというイメージです。

　しかしそんなこと言ってもいまいち理解できないため、ひとまず実際に「global」を用いたプログラムを作ってみました。

　それが、こちらのクリック数をカウントするプログラムです。

[![Image from Gyazo](https://i.gyazo.com/c65b5646c833b56f6fc45cfdb374c672.gif)](https://gyazo.com/c65b5646c833b56f6fc45cfdb374c672)

　コードはこのようになっています。あくまでも「global」の必要性を確認するために作ったので、ディテールには全くこだわっていません。

```python
from tkinter import *

count = 1

def click_count():
    global count
    txt.insert(0,count)
    txt.delete(len(str(count)),END)
    count += 1

win = Tk()
cv = Canvas(win, width = 300, height = 200)
win.title('クリック数をカウントする')
cv.pack()

txt = Entry(width = 3)
txt.place(x = 130, y = 50)

btn = Button(text='ボタン', command = click_count)
btn.place(x = 125, y = 100)
```


##なぜ「global」が必要なのか？
　このプログラムがやりたいことは、「ボタンをクリックしたらカウントが増える」というものです。単純な理屈としては以下のようなことがやりたかったわけです。ボタンを押すというアクションを起こすたびにこの関数を呼び出すのです。

```python
count = 1
def click_count():
    txt.insert(0,count)
    count += 1
```
　しかしこのようなことをすると以下のようなエラーが起きます。

```
UnboundLocalError: local variable 'count' referenced before assignment
```
　関数の中で、関数の外の「count」という変数を操作することはできないということです。変数にはどうやら「スコープ」という、使用できる範囲が決められているみたいなのです。
　では関数の中で定義するしかないのかと考え、以下のようなことをしてしまうと、当然ながら何度ボタンを押しても1しか表示されなくなります。

```python
def click_count():
    count = 1
    txt.insert(0,count)
    count += 1
```
　そんな時に使えるのが「global」というわけです。

```python
count = 1
def click_count():
    global count
    txt.insert(0,count)
    count += 1
```
　このような記述をしておけば、関数の外にある変数も操作できて一件落着なのです。

##では「nonlocal」とは何か？
　しかしながら、「global」も、適用される範囲が限られています。入れ子構造で関数が定義されている場合を見てみましょう。
　以下のサイトを参考にしてプログラムを作ってみました。

- [PRIME STUDY](https://study.prime-strategy.co.jp/)

```python
def scope():
    loc = "init"

    def do_global():
        global loc
        loc = "global"
        
    do_global()
    print("A",loc)

scope()
print("B",loc)
```
　こちらのコードの出力結果はどのようになると考えられるでしょうか？「globalを使えば関数の外でも中でも同じ変数が使えるんだ！」っていう理屈を単純に考えれば、出力結果は以下のように予想できます。

```
A global
B global
```
　ところが実際の出力結果はこうでした。

```
A init
B global
```
　①do_globalの内側 < ②scope()の内側 < ③scope()の外側と考えた時、①で定義されたglobalの変数は②では使えないみたいです。
　入れ子構造で関数が定義されている時、この②scope()の内側で使える変数を定義できるようにするものが「nonlocal」なのです。
　

```python
def scope():
    loc = "init"

    def do_nonlocal():
        nonlocal loc
        loc = "nonlocal"

    def do_global():
        global loc
        loc = "global"

    do_nonlocal()
    print("A", loc)
    do_global()
    print("B",loc)

scope()
print("C",loc)
```
　このようにdo_nonlocal()という関数を定義して呼び出した時、出力結果は以下のようになるのです。

```
A nonlocal
B nonlocal
C global
```
　do_nonlocal()を呼び出して以降、たとえdo_global()を呼び出そうが、②scope()の内側で定義されている変数「loc」は「nonlocal」という値になったのです。

　ただし、当然ながらdo_nonlocal()を呼び出す前は、scope()の中でのlocの値は「init」であるということにも注意が必要です。

```python
def scope():
    loc = "init"
    print("A",loc)

    def do_local():
        loc = "local"

    def do_nonlocal():
        nonlocal loc
        loc = "nonlocal"

    def do_global():
        global loc
        loc = "global"

    do_local()
    print("B",loc)
    do_nonlocal()
    print("C", loc)
    do_global()
    print("D",loc)

scope()
print("E",loc)
```
　このように記述を増やした場合の出力結果は以下のようになります。

```
A init
B init
C nonlocal
D nonlocal
E global
```
　
　自分自身の理解を定着される目的でこのような記事を書きました。もし気になることがございましたらご指摘ください。

<br>