クロージャ
一般に関数内部で宣言されたローカル変数は関数実行時に一次的に生成されて破棄されるので、関数の外から参照することはできません。たとえば、次のようなコードは NameError を返します。
# In[1]
def my_func():
x = 100
return x
print(x)
NameError: name 'x' is not defined
ここで、my_func() の内部に x を返すような関数 inner() を定義して、my_func() の戻り値を inner としてみます。
# In[2]
# エンクロージャ
def my_func():
x = 100
def inner(): # クロージャ
return x
return inner
このとき、my_func() を実行すると関数オブジェクト inner が返ります。
# In[3]
# my_func()を実行するとinnerが返る
f = my_func()
print(f)
<function my_func.<locals>.inner at 0x7f3632b42bf8>
変数 f には inner が入っているので、f() は inner() を実行していることになり、ローカル変数 x に割り当てられた値が戻ります。
# In[4]
# innerを実行してローカル変数xを取得
val = f()
print(val)
100
馴染めない記法ですが、my_func()() と記述しても同じです。
# In[5]
val = my_func()()
print(val)
100
inner() のように、関数内部で宣言された変数を記憶した関数のことを クロージャ (closure) とよびます。my_func() のようにクロージャを内包する関数を エンクロージャ (enclosure) といいます。
関数内に定義された関数は外側のスコープの変数を参照できますが、変数を書き換えることはできません。
# In[6]
def func_a():
x = 100
def inner():
x += 1
return x
return inner
# innerを取得
f = func_a()
# innerを実行
print(f())
UnboundLocalError: local variable 'x' referenced before assignment
スコープの外側の変数に再代入したいときは、nonlocal キーワードで外側の変数を「束縛」しておく必要があります。
# In[7]
# エンクロージャを定義
def func_b():
x = 100
def inner():
nonlocal x # 外側の変数xを束縛
x += 1
return x
return inner
# innerを取得
f = func_b()
# innerを実行
val = f()
print(val)
101
func_b のスコープの x の値が書き換えられて 101 になっています。
f() を実行する度に値は 1 つずつ加算されます。
# In[8]
print(f())
print(f())
print(f())
102 103 104
このように、クロージャを使うとカウンタ関数を実装できます。
nonlocal キーワードで外側の変数を「束縛」すると、再代入ができるようになるだけでなく、
関数から抜けてもその変数の値を保持し続けるということですね。
その通りです。
変数の値の保持については、説明が足りなかったかもれません。
後で記事本文で補足しておこうと思います。