連載
» 2019年05月28日 05時00分 公開

[Python入門]ローカル関数とラムダ式Python入門(2/3 ページ)

[かわさきしんじ,Deep Insider編集部]

ローカル関数のスコープと名前解決

 「関数内で定義したローカル変数は関数の終了時に削除される」ので、前ページで見た動作は少しおかしいと思った人もいるかもしれない。通常の関数ではそうなるのだが、関数内でローカル関数を定義した場合には少し動作が異なる。

 ローカル関数がその外側の関数で定義されているローカル変数(やパラメーター)を使用している場合、ローカル関数の定義時にそれらの値がローカル関数内で「記憶」される(名前空間にその名前が格納される)。ローカル関数が定義されるのは、その外側の関数が実行されるときなので、使用しているローカル変数やパラメーターの値はその時々で異なる。

 例えば、上の引数を変えながら、先ほど定義した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関数の呼び出しごとに、パラメーターxの値が代わり、それを使ってadder関数が定義される(それが2つの変数に代入される)

 最初のmake_adder関数呼び出しでは引数に「1」を指定しているので、adder関数の定義時にはパラメーターxの値の「1」が記憶される。次のmake_adder関数呼び出しでは、パラメーターxの値の「2」が記憶される。戻り値として渡されたそれぞれの関数を呼び出すと、それぞれの関数で独自に記憶した値が使われる。そのために、ローカル関数を定義するコード自体は同じだが、異なる値を記憶した異なる関数がmake_adder関数を呼び出すたびに作成されるというわけだ。

 念のため、実行結果を以下に示しておこう。

実行結果 実行結果

 外側の関数(この場合はmake_adder関数)のことを「エンクロージャー」と呼ぶことがある。また、エンクロージャーのローカル変数やパラメーターを使用しているローカル関数(この場合はadder関数)のことを特に「クロージャ」と呼ぶことがある。そして、クロージャがエンクロージャーのローカル変数などの値を記憶することを「変数の値をキャプチャー(捕捉)する」と表現することもある。キャプチャーされた変数の値はエンクロージャーが終了しても削除されるのではなく、キャプチャーしたクロージャが存在する限りは存在する。このために、上のような動作が可能になっている。

 ローカル関数やクロージャのスコープは次の4つになることにも注意しよう。

  1. 関数のローカルスコープ
  2. その外側(エンクロージャー)のローカルスコープ
  3. グローバルスコープ(モジュールスコープ)
  4. ビルトインスコープ

 そして、ローカル関数で使われている名前を解決するときには、上に挙げた順に名前空間が検索される。

nonlocal文

 前回に「変数に代入をするときには、通常、ローカルスコープが使われる」といったことを述べたが、これはローカル関数でも変わらない。つまり、ローカル関数では、その外側にあるエンクロージャーのローカルスコープやグローバルスコープで定義されている変数の値を「参照」することはできても、「変更」することは通常は行えない。試しに、呼び出された回数を返すだけの関数(を戻りとする関数)を作成してみよう。

def make_counter():
    count = 0
    def counter():
        count = count + 1
        return count
    return counter

呼び出し回数を数えるだけの関数(を定義する関数)

 考えていることはこうだ。

  • make_counter関数でローカル変数countを定義しておく
  • counter関数ではその値を記憶しておく
  • counter関数が呼び出されるたびに、記憶した変数countの値を1ずつ増やし、それを返す

 単純な関数では、ローカル変数の値は関数終了時に削除されてしまうので、ローカル関数を作成することで呼び出し回数を数える変数を記憶できるようにしている。これを呼び出すコードは次のようになる。

countup = make_counter()
print(countup())
print(countup())

呼び出し回数を数える関数の呼び出し

 実際に実行してみると、次のようになる。

「ローカル変数countが、代入(定義)よりも前に参照されている」というエラーが発生した 「ローカル変数countが、代入(定義)よりも前に参照されている」というエラーが発生した

 「ローカル変数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

修正後のmake_counter関数

 ノンローカル宣言を行ったことで、counter関数内に登場する「count」という名前はその外側のmake_counter関数で定義されているローカル変数countを参照し、その値を変更できるようになる。これにより、以下のコードを実行したときに関数の呼び出し回数がカウントされるようになる。

countup = make_counter()
print(countup())
print(countup())

呼び出し回数を数える関数の呼び出し

 実行結果を以下に示す。

呼び出し回数が数えられるようになった 呼び出し回数が数えられるようになった

 なお、前回と今回に見てきたように、関数内で定義されていないが、使われている変数のことを「自由変数」と呼ぶことがある。上の例なら変数countは自由変数だ。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。