クロージャ (closures) は、Pythonの強力なメカニズムで、周囲のコンテキストから "記憶された" データを持つ関数を作成することを可能にします。
関数型言語やPythonでは、関数は第一級オブジェクトです。関数は、外部のスコープから変数を "閉じ込める" 新しい関数を返すことができます。
開発者はしばしば、クロージャ内部で閉じ込められた変数がどのように "存在する"のか、変数が読み書きされたときにどのように扱うべきか(特に可変オブジェクトについて)、混乱することがあります。
内部関数が外部関数の変数を使用する場合、外部関数がすでに終了したとしても、それらは自動的に "記憶されます"。変数を読み取るのは明白ですが、値を変更する必要がある場合は、nonlocalキーワードを使用する必要があります。可変オブジェクト(リスト、辞書)を扱う場合、これは特にリスクの高い領域です。
例:
def outer(): count = 0 def inner(): nonlocal count count += 1 return count return inner counter = outer() print(counter()) # 1 print(counter()) # 2
主な特徴:
nonlocal を使用してください(そうしないと、ローカル変数が作成されます)。nonlocalなしで関数内で閉じ込められた変数の値を変更できますか?
いいえ。新しい値を代入しようとすると、nonlocalを指定しない限り、Pythonはこれを新しいローカル変数の作成と解釈し、古い値は外に出ません。
例:
def make_counter(): count = 0 def inner(): count += 1 # UnboundLocalError! のエラー return count return inner
クロージャを通じて外部スコープに引数を渡すことはできますか?
はい、クロージャは外部スコープで利用可能な変数を "記憶します"。クラスの属性やグローバル変数なども含まれます。しかし、これらの変数を変更するには特別な努力が必要です(例えば、nonlocalまたはglobalの使用)。
クロージャ内部の可変オブジェクトで何が起こりますか?
もし、可変オブジェクトへの参照を持つ変数が閉じ込められた場合、たとえばリストの場合、その内容をnonlocalなしで変更できますが、変数を再代入しようとすると、nonlocalが必要です。
例:
def make_appender(): result = [] def append(x): result.append(x) # OK! return result return append f = make_appender() print(f(1)) # [1] print(f(2)) # [1, 2]
開発者がクロージャを記述し、nonlocalなしで変数を変更し、UnboundLocalErrorが発生します。
利点:
欠点:
クロージャ内の状態を管理するためにnonlocalを明示的に使用します。
利点:
欠点: