『Python数値計算ノート』ではアフィリエイトプログラムを利用して商品を紹介しています。

クロージャとエンクロージャ

クロージャ

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

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

コメント

  1. HNaito より:

    nonlocal キーワードで外側の変数を「束縛」すると、再代入ができるようになるだけでなく、関数から抜けてもその変数の値を保持し続けるということですね。

    • あとりえこばと より:

      その通りです。
      変数の値の保持については、説明が足りなかったかもれません。
      後で記事本文で補足しておこうと思います。