僕がPythonを勉強し始めて最初に「何だこれ?」と感じたのが「global」と「nonlocal」でした。
簡単に説明するなら、どちらも「同じ変数を関数の中でも外でも使いたい時」に用いるものです。そして関数が入れ子構造で定義されているときに「nonlocal」が使われるというイメージです。
しかしそんなこと言ってもいまいち理解できないため、ひとまず実際に「global」を用いたプログラムを作ってみました。
それが、こちらのクリック数をカウントするプログラムです。
コードはこのようになっています。あくまでも「global」の必要性を確認するために作ったので、ディテールには全くこだわっていません。
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」が必要なのか?
このプログラムがやりたいことは、「ボタンをクリックしたらカウントが増える」というものです。単純な理屈としては以下のようなことがやりたかったわけです。ボタンを押すというアクションを起こすたびにこの関数を呼び出すのです。
count = 1
def click_count():
txt.insert(0,count)
count += 1
しかしこのようなことをすると以下のようなエラーが起きます。
UnboundLocalError: local variable 'count' referenced before assignment
関数の中で、関数の外の「count」という変数を操作することはできないということです。変数にはどうやら「スコープ」という、使用できる範囲が決められているみたいなのです。
では関数の中で定義するしかないのかと考え、以下のようなことをしてしまうと、当然ながら何度ボタンを押しても1しか表示されなくなります。
def click_count():
count = 1
txt.insert(0,count)
count += 1
そんな時に使えるのが「global」というわけです。
count = 1
def click_count():
global count
txt.insert(0,count)
count += 1
このような記述をしておけば、関数の外にある変数も操作できて一件落着なのです。
##では「nonlocal」とは何か?
しかしながら、「global」も、適用される範囲が限られています。入れ子構造で関数が定義されている場合を見てみましょう。
以下のサイトを参考にしてプログラムを作ってみました。
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」なのです。
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」であるということにも注意が必要です。
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
自分自身の理解を定着される目的でこのような記事を書きました。もし気になることがございましたらご指摘ください。