クロージャ

クロージャ

クロージャ

 一般に関数内部で宣言されたローカル変数は関数実行時に一次的に生成されて破棄されるので、関数の外から参照することはできません。たとえば、次のようなコードは 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

 このように、クロージャを使うとカウンタ関数を実装できます。