「クロージャ」という用語は関数型プログラミングから借用され、Pythonの初期から存在しています。クロージャを使用すると、関数は作成された環境を記憶することができ、その環境の外で呼び出されてもその環境を保持します。この概念は柔軟性を提供し、多くのパターンを実現可能にします。例えば、関数ファクトリや遅延計算などです。
Pythonでは、関数は第一級オブジェクトです。時々、内側の関数が外側の関数のスコープ内の変数を使用する必要がありますが、外側の関数が終了した後でもその変数にアクセスできることが必要です。通常のレキシカルスコープでは、外側の関数が戻る際にこれが保証されません。こうした場合、変数にアクセスすることでクロージャが形成されます。
クロージャが発生するのは、内部関数が外部に定義された変数を参照し、その外部関数がその内部関数を外部に返した時です。これは、関数ファクトリを作成し、クラスなしで状態をカプセル化し、「その場」でパラメータを使用した関数を構築するためによく使われます。
def make_multiplier(factor): def multiplier(x): return x * factor return multiplier mul2 = make_multiplier(2) mul3 = make_multiplier(3) print(mul2(10)) # 20 print(mul3(10)) # 30
nonlocalキーワードを使用します。クロージャは、変数が内部関数内で変更されるとき、呼び出し間で可変状態を保持できますか?
はい、内部関数内でnonlocalキーワードを使用すれば可能です。nonlocalなしで代入すると、新しいローカル変数が作成され、外部変数は変更されません。
def counter(): count = 0 def inc(): nonlocal count count += 1 return count return inc c = counter() print(c()) # 1 print(c()) # 2
クロージャを使用してPythonでプライベート変数を実装できますか?クラスの代わりに使用することは可能ですか?
はい、クロージャは外部からアクセスできない「プライベート」変数の簡単な実装を提供します。ただし、内部関数にゲッター/セッターが提供されない場合に限ります。
クロージャは関数にのみ適用されますか? Pythonでラムダを使用してクロージャを形成できますか?
はい、クロージャはラムダ式でも形成されることがあります。なぜなら、ラムダはレキシカル変数のバインディングにおいてdefと同様だからです。
def make_power(n): return lambda x: x ** n square = make_power(2) cube = make_power(3) print(square(4)) # 16 print(cube(2)) # 8
nonlocalなしで内部変数を変更すると、クロージャが自動的に外部変数を変更すると期待する。ループ内でハンドラーを生成するファクトリ関数が、クロージャ内でループ変数を使用している場合:
handlers = [] for i in range(3): def handler(x): return x + i handlers.append(handler) print([h(10) for h in handlers]) # [12, 12, 12]
利点:
欠点:
iを参照し、最後の値は2であり、大多数にとって予期しない動作をします。デフォルト引数を使用して値を「固定」します:
handlers = [] for i in range(3): def handler(x, j=i): return x + j handlers.append(handler) print([h(10) for h in handlers]) # [10, 11, 12]
利点:
欠点: