クロージャ
一般に関数内部で宣言されたローカル変数は関数実行時に一次的に生成されて破棄されるので、関数の外から参照することはできません。たとえば、次のようなコードは 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 キーワードで外側の変数を「束縛」すると、再代入ができるようになるだけでなく、関数から抜けてもその変数の値を保持し続けるということですね。
その通りです。
変数の値の保持については、説明が足りなかったかもれません。
後で記事本文で補足しておこうと思います。