変数のスコープとクロージャ
単価を保持する関数を複数作り、定義した場所と呼び出した場所の違いを確認します。globalとnonlocalによる更新も試し、どの呼出しに変更が残るかを追います。
説明
同じ名前でも、関数内と外側では別の値を指すことがあります。Pythonは通常、ローカル、定義を囲む関数、グローバル、組込みの順で名前を探します。
単価を保持する関数を複数作り、定義した場所と呼び出した場所の違いを確認します。globalとnonlocalによる更新も試し、どの呼出しに変更が残るかを追います。
1対話セッションの名前
最上位のrateを100へ設定します。このモジュールでは対話セッションの最上位をグローバルと呼びます。
rate = 100グローバルの単価; print(rate)外側から参照後で関数の引数にも同じrateという名前を使います。同じ綴りが出たときに、どの範囲の名前かを確認します。
2単価を保持した関数を作る
make_quoteのrateは仮引数です。内側のquoteは、その呼出しで受け取ったrateを使います。
def make_quote(rate):呼出しごとの単価
def quote(quantity):内側の関数
return rate * quantity定義を囲むrateを参照
return quote関数を返すmake_quoteは計算結果ではなく関数quoteを返します。内側のquoteが外側のrateを参照するため、make_quoteが終了した後もその環境を利用できます。このような関数をクロージャと呼びます。
3別々の呼出しで環境を作る
単価120と150の関数を作り、グローバルのrateは999へ変えます。
first = make_quote(120); second = make_quote(150)異なる環境を持つ二関数; rate = 999グローバルを変更; print(first(2), second(2), rate)保持した単価と外側の値240、300、999です。二つの関数は別々の呼出しのrateを使い、グローバルrateの再代入では変わりません。内側の名前が外側の同名の値を隠していても、外側を上書きしたわけではありません。
4呼出元にも同名の値を置く
caller内でrate=1にしてからfirstを呼びます。
def caller():
rate = 1呼出元だけのローカル
return first(2), rate保持した関数と呼出元の値firstが使うrateは、呼出元callerのローカルではありません。名前の探索は、関数を定義した入れ子の関係に従います。
5定義場所の環境が使われるか
callerを実行し、グローバルrateも確認します。
print(caller(), rate)呼出し先・呼出元・グローバルfirstは単価120を使って240を返し、callerのrateは1、グローバルは999です。同名の三つの値が別の範囲に存在しています。
6関数内の代入でローカルになる
宣言なしでrateを+=しようとする関数を定義します。
def broken():
rate += 1未初期化のローカルを読む
return rate更新後を返す意図代入があるため、この関数のrateはローカルとして扱われます。+=は現在値を読んでから更新しますが、ローカルのrateにはまだ値を代入していません。
7外側の値があってもエラーになる
brokenを呼びます。
broken()未初期化のローカル参照UnboundLocalErrorになります。グローバルに999があっても、関数内で代入されるローカルrateを読む代わりには使われません。
8globalで更新対象を指定する
同じ更新をグローバルへ行う関数を定義します。
def bump_global():
global rate最上位の名前を更新対象にする
rate += 1グローバルへ再代入
return rate更新した値globalは、この関数のrateの代入先を最上位の名前に指定します。共有する名前を変更するため、他の処理への影響も確認する必要があります。
9共有する名前の変更を確認
関数を呼び、最上位の値とクロージャの結果も表示します。
print(bump_global(), rate, first(2))更新対象と保持された単価を比較グローバルは1000へ変わりますが、firstの単価120は変わらず240です。名前の綴りが同じでも、保持されている環境は別です。
10nonlocalで外側の関数の値を更新する
カウンターごとにvalueを保持し、内側の関数からnonlocalで更新します。
def make_counter(start):
value = start生成した呼出しの状態
def increment():
nonlocal value外側の関数の名前を更新
value += 1保持した状態を変更
return value
return increment状態を使う関数を返すnonlocalは定義を囲む関数のvalueを更新します。単純な値のコピーを返しているのではなく、その後も更新できる束縛を内側の関数が保持しています。
11別々のカウンターを呼び分ける
開始10と100のカウンターを作り、一つ目を二回呼んでから二つ目を呼びます。
a = make_counter(10); b = make_counter(100)別の保持環境を作る; print(a(), a(), b(), a())呼出し後も残る状態11、12、101、13です。aの呼出し間では状態が残り、bは別の環境で進みます。関数を複数作ったからといって全てが一つの状態を共有するわけではありません。
まとめ
名前は呼出元の同名変数ではなく、定義した範囲に従って参照されます。
globalとnonlocalの更新対象を分け、クロージャごとの保持状態を確認します。
操作履歴と解説を振り返れます。
Python 3
rate = 100; print(rate)100
def make_quote(rate):
def quote(quantity):
return rate * quantity
return quotefirst = make_quote(120); second = make_quote(150); rate = 999; print(first(2), second(2), rate)240 300 999
def caller():
rate = 1
return first(2), rateprint(caller(), rate)(240, 1) 999
def broken():
rate += 1
return ratebroken()Traceback (most recent call last): File "<console>", line 1, in <module> File "<console>", line 2, in broken UnboundLocalError: cannot access local variable 'rate' where it is not associated with a value
def bump_global():
global rate
rate += 1
return rateprint(bump_global(), rate, first(2))1000 1000 240
def make_counter(start):
value = start
def increment():
nonlocal value
value += 1
return value
return incrementa = make_counter(10); b = make_counter(100); print(a(), a(), b(), a())11 12 101 13
演習完了