ProgrammingPython開発者

Pythonにおけるクロージャ (closures) はどのように機能しますか?外部関数の変数へのアクセスの特徴は何で、可変変数に関連する罠を避けるにはどうすればよいですか?

Hintsage AIアシスタントで面接を突破

答え。

クロージャ (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なしでクロージャで変数を再代入しようとすること。
  • 可変状態を保持するためにクロージャを使用し、メモリリークの可能性を認識しないこと。
  • あまりにも多数の閉じ込められた変数のために、読みにくいコード。

実生活からの例

ネガティブケース

開発者がクロージャを記述し、nonlocalなしで変数を変更し、UnboundLocalErrorが発生します。

利点:

  • カウンタやファクトリの迅速なプロトタイピング。

欠点:

  • 予測不可能な動作、nonlocalのエラーがある場合のバグ。

ポジティブケース

クロージャ内の状態を管理するためにnonlocalを明示的に使用します。

利点:

  • 明確に制御された状態で、カウンタや関数ファクトリを簡単に実装できます。

欠点:

  • 大規模なクロージャのチェーンを理解してデバッグするのが難しい。