クロージャ
一般に関数内部で宣言されたローカル変数は関数実行時に一次的に生成されて破棄されるので、関数の外から参照することはできません。たとえば、次のようなコードは 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 キーワードで外側の変数を「束縛」すると、再代入ができるようになるだけでなく、
関数から抜けてもその変数の値を保持し続けるということですね。
その通りです。
変数の値の保持については、説明が足りなかったかもれません。
後で記事本文で補足しておこうと思います。
【GPT解説】クロージャは関数が作成された環境を保持します
クロージャ(Closure)は、Pythonの中で関数とその関数が作成された環境(変数など)を包括する仕組みです。この概念を理解すると、より柔軟で強力な関数の作成が可能になります。まず、基本的な関数の定義から始めましょう。
ここで、outer_function は引数 x を受け取り、inner_function を返す関数です。inner_function は引数 y を受け取り、x と y を足した値を返します。しかし、ここで注目すべきは、inner_function 内部で使われている変数 x です。通常、関数内部で使われる変数は、その関数が呼び出されるたびに新しく生成されますが、クロージャを使うことで、inner_function は outer_function の引数 x を覚えています。このため、inner_function を outer_function の外から呼び出しても、x の値が保持されたままになります。具体的な使用例を見てみましょう。
この例では、outer_function(10) によって x の値が10であるクロージャが作成され、それを closure_instance という変数に格納しています。そして、closure_instance(5) を呼び出すことで、内部の inner_function が呼び出され、x(ここでは10)と y(ここでは5)が足し合わされて15が返されます。このように、クロージャを使用することで、外部の関数が呼び出されたときに、その関数が作成された環境(変数の値など)を保持しながら動作する関数を作成することができます。これは、特定の状況で非常に便利な場合があります。