一覧PG-07-1 · スコープと参照

変数のスコープとクロージャ

単価を保持する関数を複数作り、定義した場所と呼び出した場所の違いを確認します。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

1
rate = 100; print(rate)
100
2
def make_quote(rate): def quote(quantity): return rate * quantity return quote
3
first = make_quote(120); second = make_quote(150); rate = 999; print(first(2), second(2), rate)
240 300 999
4
def caller(): rate = 1 return first(2), rate
5
print(caller(), rate)
(240, 1) 999
6
def broken(): rate += 1 return rate
7
broken()
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
8
def bump_global(): global rate rate += 1 return rate
9
print(bump_global(), rate, first(2))
1000 1000 240
10
def make_counter(start): value = start def increment(): nonlocal value value += 1 return value return increment
11
a = make_counter(10); b = make_counter(100); print(a(), a(), b(), a())
11 12 101 13

演習完了