クロージャの説明
クロージャ(関数閉方)とは外側の変数を記憶した関数です。
# 例1
def func():
x = 3
def add3(y):
return y+x
return add3
f = func()
print(f(4)) # 7
func 関数は add3 関数を定義してそれを返します。
add3 関数は外側で定義された x を関数内で使用しています。
add3 関数をクロージャ、func 関数をエンクロージャといいます。
x は本来であれば func関数を抜けたところで消滅するfunc 関数におけるローカル変数です。
クロージャを生成したことで x が add3 関数内に記憶されて f(4) で呼び出した時に x が加算されます。
# 例2
def func(name):
def add_msg(message):
return "Hi " + name + ":" + message
return add_msg
f = func("Tanaka")
print(f("how are you?")) # Hi Tanaka:how are you?
例2は add_msg 関数がクロージャで name 変数を記憶したものが func 関数から生成されています。
# 例2の続き
print(f.__code__.co_freevars[0], f.__closure__[0].cell_contents) # name Tanaka
関数 f に記憶されている変数名とその値を確認できます。
尚、クロージャには当然2個以上の変数も記憶できるので、エンクロージャの環境を記憶すると説明されることがあります。
自由変数 と 束縛(バインド)
Pythonの自由変数とはある関数内でローカル変数と自身の引数以外で使われている変数です。
例1で x は add3 の中において 自由変数 です。
同様に例2で name は add_msg にとって 自由変数です。
束縛とは名前を定義することです。
n = "Tanaka" とすれば この代入対象の識別子 n は 束縛されます。
関数を定義すれば関数名が束縛されますし、クラスを定義すればクラス名が束縛されます。
import文ではモジュール名が束縛されます。
つまり名前が何らかのオブジェクトに紐付けされることです。
また自由変数がクロージャ内に記憶されることをクロージャに束縛されると言います。
束縛された変数の更新
# 例3
def func():
x = 3
def value(v):
nonlocal x
x = v
def add(y):
return y+x
x = 5
return value, add
v, f = func()
print(f(4)) # 9 = 5 + 4
v(10) # x の値が 5 → 10 に更新される
print(f(4)) # 14 = 4 + 10
束縛された変数の値を後から更新する必要がある場合は Python 3 で導入された nonlocal で宣言します。
例3では自由変数 x を nonlocal で宣言しておくことで後から value 関数を経由して add 関数内でも束縛されている x を更新しています。
# 例3の続き
print(v.__closure__[0].cell_contents is f.__closure__[0].cell_contents) # True
クロージャvで束縛されている変数 x とfで束縛される変数 x は同一オブジュエクトであることが判ります。
静的(レキシカル)スコープ
クロージャは静的スコープの言語で成り立ちます。
静的スコープとは変数の名前解決がソースコード上で成立するものいいます。
C, Python, Javascriptなどは静的スコープの言語です。
逆に動的スコープとは変数の名前解決が実行時のコースタックにて行われます。
Emacs Lisp, bash シェルスクリプト, powershell などは動的スコープです。
PerlやLispでは定義時に静的スコープか動的スコープかを選択できます。