以前、こちらのネストした関数定義のユースケースの記事で、クロージャについて書きましたが、フリー変数の名前や値が、関数オブジェクトの中でどのように保存されているかを調べてみました。(以下、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."