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 がどう動作するのかが明確になります。