以前、こちらのネストした関数定義のユースケースの記事で、クロージャについて書きましたが、フリー変数の名前や値が、関数オブジェクトの中でどのように保存されているかを調べてみました。(以下、Python3を前提にしています。)
Pythonのフリー変数は、(グローバル変数でなく、かつ)コードブロックの中で使われているけれども、そのブロック内で定義されていない変数と説明されています。
例えば下記のコードでは、変数yはネストしたbar関数内で使用されていますが、定義されているのはその外側のfoo関数の定義ブロック内です。なので、yは関数fooのローカル変数で、bar関数のフリー変数です。
def foo():
y = "I'm free in bar."
def bar():
print(y)
y = "I'm still free in bar."
return bar
fooはbarを返すので、barを呼ぶには、次のように括弧が二つ必要です。
>>> foo()()
I'm still free in bar.
ちなみに、yの値は、barが定義された時点ではなく、呼ばれた時点の値になります。上の例ではyはfooの中で二回定義されていますが、後に定義された文字列になっているのがわかります。
さて、本題ですが、外側の関数fooオブジェクト側では、ネストした関数から使用されている変数名yは、自身のコードオブジェクト(__code__)のco_cellvarsメンバ(tuple)に入っています。
>>> foo.__code__.co_cellvars
('y',)
一方で、barオブジェクト側では、yはフリー変数としてコードオブジェクトのco_freevarsメンバの中に名前が入っています。(下記のfoo()はbarです。)
>>> foo().__code__.co_freevars
('y',)
フリー変数の中身は、cellオブジェクトとして__closure__属性のtupleに入っており、次のようにcell_contentsとして取得できます。
>>> foo().__closure__[0].cell_contents
"I'm still free in bar."