「関数内で定義したローカル変数は関数の終了時に削除される」ので、前ページで見た動作は少しおかしいと思った人もいるかもしれない。通常の関数ではそうなるのだが、関数内でローカル関数を定義した場合には少し動作が異なる。
ローカル関数がその外側の関数で定義されているローカル変数(やパラメーター)を使用している場合、ローカル関数の定義時にそれらの値がローカル関数内で「記憶」される(名前空間にその名前が格納される)。ローカル関数が定義されるのは、その外側の関数が実行されるときなので、使用しているローカル変数やパラメーターの値はその時々で異なる。
例えば、上の引数を変えながら、先ほど定義したmake_adder関数を何度か呼び出してみよう。
one_adder = make_adder(1) # 「1」を加算する関数を作成
two_adder = make_adder(2) # 「2」を加算する関数を作成
print(one_adder(100)) # 出力:「101」
print(two_adder(100)) # 出力:「102」
最初のmake_adder関数呼び出しでは引数に「1」を指定しているので、adder関数の定義時にはパラメーターxの値の「1」が記憶される。次のmake_adder関数呼び出しでは、パラメーターxの値の「2」が記憶される。戻り値として渡されたそれぞれの関数を呼び出すと、それぞれの関数で独自に記憶した値が使われる。そのために、ローカル関数を定義するコード自体は同じだが、異なる値を記憶した異なる関数がmake_adder関数を呼び出すたびに作成されるというわけだ。
念のため、実行結果を以下に示しておこう。
外側の関数(この場合はmake_adder関数)のことを「エンクロージャー」と呼ぶことがある。また、エンクロージャーのローカル変数やパラメーターを使用しているローカル関数(この場合はadder関数)のことを特に「クロージャ」と呼ぶことがある。そして、クロージャがエンクロージャーのローカル変数などの値を記憶することを「変数の値をキャプチャー(捕捉)する」と表現することもある。キャプチャーされた変数の値はエンクロージャーが終了しても削除されるのではなく、キャプチャーしたクロージャが存在する限りは存在する。このために、上のような動作が可能になっている。
ローカル関数やクロージャのスコープは次の4つになることにも注意しよう。
そして、ローカル関数で使われている名前を解決するときには、上に挙げた順に名前空間が検索される。
前回に「変数に代入をするときには、通常、ローカルスコープが使われる」といったことを述べたが、これはローカル関数でも変わらない。つまり、ローカル関数では、その外側にあるエンクロージャーのローカルスコープやグローバルスコープで定義されている変数の値を「参照」することはできても、「変更」することは通常は行えない。試しに、呼び出された回数を返すだけの関数(を戻りとする関数)を作成してみよう。
def make_counter():
count = 0
def counter():
count = count + 1
return count
return counter
考えていることはこうだ。
単純な関数では、ローカル変数の値は関数終了時に削除されてしまうので、ローカル関数を作成することで呼び出し回数を数える変数を記憶できるようにしている。これを呼び出すコードは次のようになる。
countup = make_counter()
print(countup())
print(countup())
実際に実行してみると、次のようになる。
「ローカル変数countが、代入する前に参照されている」というエラーが発生している。「count = count + 1」という代入文では、make_counter関数で定義されている変数countを参照しているつもりなのだが、先ほども述べた通り、「変数の代入にはローカルスコープ」が使われる。そのため、これはcounter関数内でのローカル変数countの定義となる。そして、ローカル変数countへ代入しようとしている値は「count + 1」となっていて、まだ定義できていない自分自身の値を参照しようとしていると解釈されたのだ。
関数内部でグローバル変数の値を変更するにはglobal文を使用したが、クロージャでエンクロージャーのローカル変数を変更するにはnonlocal文を使用してノンローカル宣言を行う。ノンローカル宣言するには、global文と同様に、「nonlocal」に続けて対象の変数を列挙すればよい。上のコードを修正すると次のようになる。
def make_counter():
count = 0
def counter():
nonlocal count
count = count + 1
return count
return counter
ノンローカル宣言を行ったことで、counter関数内に登場する「count」という名前はその外側のmake_counter関数で定義されているローカル変数countを参照し、その値を変更できるようになる。これにより、以下のコードを実行したときに関数の呼び出し回数がカウントされるようになる。
countup = make_counter()
print(countup())
print(countup())
実行結果を以下に示す。
なお、前回と今回に見てきたように、関数内で定義されていないが、使われている変数のことを「自由変数」と呼ぶことがある。上の例なら変数countは自由変数だ。
Copyright© Digital Advantage Corp. All Rights Reserved.