はじめに
今回の質問(Qiitaでの質問ではありません)
クロージャで外側の変数が参照できるというのは理解できるが納得はできない。関数内の変数1(引数含む)は関数が終了したら消えるのではないか?変数はどこかに保持されてるのだろうけど具体的にどこにあるのか。
これに対して
「まずはそういうものだと思わないといけない。それ以上知りたいのならPythonのソースを読まないといけない。ちなみに関数(クロージャ)に結び付けられている」
と答えたのですが、もう少し考えたところ、
「そもそも関数が終了したら変数が消えるという概念自体どこから学んだのか(消えるけど)。なお言語実装に関する知識(つまりスタック上に変数が確保される2という知識)はないものとする」
ということが気になりました。
そんなわけで今回はここら辺の概念を絵で説明しようと思います。
関数と変数と関数の終わり
クロージャの前にまず普通の関数です。
def add(a, b):
return a + b
add(2, 3)
最終行ではa
を2、b
を3としてのadd
の呼び出しが行われます。これを図に描くと以下のようになります。
「addの実行状態」としているのはこの後で説明する「関数が終了する際の動作」のためですが(「add」とすると話が正確でなくなります)、ともかく「2」というオブジェクトに対して実行状態からa
の矢印、「3」というオブジェクトに対してb
の矢印が出ています。a, bが上下逆なのはこの後で描く図に合わせるためなのであまり気にしないでください。
add
が終了するときには「addの実行状態」が消えます。すると当然実行状態から出ているa
, b
の矢印も消えます。これが「関数が終了すると変数が消える」状況です。
クロージャと変数
さてここまでで「関数が実行されるときの変数のイメージ」、「関数が終了するときの変数のイメージ」を説明してきました。これを踏まえてクロージャに進みましょう。なお無駄なb
変数がいますが話の都合です。
def make_adder(a, b):
def adder(x):
return x + a
return adder
adder3 = make_adder(2, 3)
adder3(4)
make_adder(2, 3)
呼び出しが行われ、make_adder
のreturn
手前(つまりadder
が定義された後)時点を図に描くと以下のようになります。つまり、a
が指している「2」はmake_adder
(の実行状態)からもadder
からも参照されていることになります。3
make_adder
が終了するときには普通の変数と同様に実行状態が消えます。ただし、青で書いているadder
本体はreturnされているので消えません。そして、adder
から参照されているa
(が指す「2」)も消えません。
このようにしてa
(が参照しているもの)が残るため、その後にadder3(4)
と呼び出されると保存されていた「2」を取り出して使うことが可能になります。
おしまい。
あっ、nonlocalは多分もう少しめんどくさいことしてると思いますが調べたことないので省きます。
参考
【Python】クロージャ(関数閉方)とは
変数がどう保存されているのか等について詳しく書かれています。
Pythonを読む/クロージャ
2年半ほど前に「クロージャの実装詳細」を読んだ時のメモです。