Pythonにおける「クロージャ(Closure)」は、関数型プログラミングの重要な概念の一つです。簡単に言えば、**「自分自身が定義された環境(スコープ)にある変数を保持し続ける関数」**のことを指します。
通常、関数内で定義されたローカル変数は、関数の実行が終了するとメモリから破棄されます。しかし、クロージャを使用すると、関数の実行が終了した後でも、その関数内の変数の値を保持(記憶)し続けることができます。これは、クラスを使わずに「状態」を持つ仕組みを作りたい場合に非常に有効です。
この記事では、クロージャの基本的な仕組みと、変数を更新するために必要な nonlocal 文について解説します。
クロージャの基本構造
クロージャを作成するには、以下の条件を満たす必要があります。
- 入れ子関数(ネスト関数): 関数の中に、別の関数(内部関数)を定義する。
- 外部変数の参照: 内部関数が、外側の関数の変数を参照している。
- 関数の返却: 外側の関数が、戻り値として「内部関数そのもの」を返す。
具体的なコード例:乗算器の作成
例として、指定した倍率で数値を掛け合わせる関数を生成する「ファクトリー関数」を作成します。
def create_multiplier(factor):
"""
指定された倍率(factor)を保持するクロージャを作成して返す
"""
# --- 内部関数 ---
def multiplier(number):
# 外側の関数の変数 factor を参照している
return number * factor
# ----------------
# 内部関数を実行せずに、関数オブジェクトとして返す
return multiplier
# 1. 「3倍にする関数」を作成
times_three = create_multiplier(3)
# 2. 「5倍にする関数」を作成
times_five = create_multiplier(5)
# 生成された関数を実行
print(f"10を3倍: {times_three(10)}")
print(f"10を5倍: {times_five(10)}")
# 関数が保持している環境を確認(参考)
print(f"保持している値: {times_three.__closure__[0].cell_contents}")
実行結果:
10を3倍: 30
10を5倍: 50
保持している値: 3
解説
create_multiplier(3) の実行が終了した時点で、本来であれば引数 factor は破棄されるはずです。しかし、戻り値である times_three 関数(実体は multiplier)は、自分が定義された時の環境である factor=3 という状態を保持し続けています。これがクロージャです。
状態を更新する:nonlocal 宣言
クロージャで保持している変数を「参照」するだけでなく、「更新(書き換え)」したい場合には注意が必要です。
Pythonでは、関数内で変数に代入を行うと、それは自動的に「その関数内のローカル変数」とみなされます。そのため、内部関数から外側の変数を書き換えようとすると、新しい変数が作られてしまい、意図した動作になりません(あるいはエラーになります)。
外側の変数を明示的に更新するためには、nonlocal 宣言を使用します。
具体的なコード例:貯金箱(状態の更新)
呼び出すたびに合計金額を加算していく「貯金箱」のような機能を作成します。
def create_wallet(initial_balance):
"""
残高を保持し、預金するたびに更新するクロージャを返す
"""
balance = initial_balance
def deposit(amount):
# 外側の変数 balance を更新することを宣言
nonlocal balance
balance += amount
print(f"{amount}円 預金しました。残高: {balance}円")
return balance
return deposit
# 1000円持っている状態のウォレットを作成
my_wallet = create_wallet(1000)
# 関数を呼び出すたびに、balance の状態が更新・保持される
my_wallet(500)
my_wallet(200)
my_wallet(1000)
実行結果:
500円 預金しました。残高: 1500円
200円 預金しました。残高: 1700円
1000円 預金しました。残高: 2700円
nonlocal がない場合
もし上記のコードで nonlocal balance を記述しなかった場合、balance += amount の行で UnboundLocalError(変数が定義前に参照された)が発生します。Pythonは balance を deposit 関数内の新しいローカル変数と解釈しますが、初期化前に加算しようとするためです。
まとめ
- クロージャ: 外側の関数の変数を記憶したまま、その環境を持ち運べる内部関数のこと。
- 作成方法: 関数の中で関数を定義し、その内部関数を戻り値として返します。
- 状態の保持: クラスを使わずに、データ(状態)と処理(関数)をセットで扱えます。
nonlocal: クロージャ内で保持している変数の値を「変更」する場合に必須の宣言です。
クロージャは、デコレータの実装や、コールバック関数の状態管理など、Pythonの高度な機能の基礎となっています。
