Python の自由変数とセル変数(__closure__)の仕組み
クロージャは外側のスコープの変数を参照する内部関数です。その仕組みは __closure__ 属性、自由変数、セル変数によって実現されています。
クロージャの基本
def outer(x):
def inner(y):
return x + y
return inner
add_10 = outer(10)
print(add_10(5)) # 15
inner 関数は outer のローカル変数 x を参照しています。outer が終了しても x の値は保持されます。
closure 属性
クロージャが参照する外部変数は __closure__ に格納されます。
print(add_10.__closure__)
# (<cell at 0x...: int object at 0x...>,)
print(add_10.__closure__[0].cell_contents) # 10
自由変数とセル変数
自由変数(free variable)
内部関数から見た、外側スコープの変数。co_freevars に記録。
セル変数(cell variable)
外部関数から見た、内部関数に渡す変数。co_cellvars に記録。
def outer(x):
def inner(y):
return x + y
return inner
# 外側から見ると x はセル変数
print(outer.__code__.co_cellvars) # ('x',)
# 内側から見ると x は自由変数
inner = outer(10)
print(inner.__code__.co_freevars) # ('x',)
セルオブジェクトの仕組み
セルは変数への間接参照を提供します。これにより、外側関数が終了しても変数を生存させられます。
def make_counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
counter = make_counter()
print(counter()) # 1
print(counter()) # 2
print(counter.__closure__[0].cell_contents) # 2
複数の自由変数
def outer(a, b):
def inner(c):
return a + b + c
return inner
fn = outer(1, 2)
print(fn.__code__.co_freevars) # ('a', 'b')
print([cell.cell_contents for cell in fn.__closure__]) # [1, 2]
クロージャの内部構造を理解すると、なぜ変数が保持されるのか、nonlocal がどう動作するのかが明確になります。