クロージャは外側のスコープの変数を参照する内部関数です。その仕組みは __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 がどう動作するのかが明確になります。