「Pythonの基礎 - クロージャ」の版間の差分
細 文字列「<source」を「<syntaxhighlight」に置換 |
編集の要約なし |
||
| (同じ利用者による、間の1版が非表示) | |||
| 1行目: | 1行目: | ||
== 概要 == | == 概要 == | ||
クロージャとは、参照環境を伴った関数、あるいはその関数への参照のことを指す。<br> | |||
クロージャは、関数内関数やC / C++ / C#の関数オブジェクトに似ている。<br> | |||
<br> | <br> | ||
関数内の変数を扱う時、その関数が宣言された時のスコープによって実行されるというような説明もできる。<br> | |||
クロージャは、外側の関数の変数を「記憶」して、後から実行することができる強力な機能である。<br> | |||
<br> | |||
主な用途:<br> | |||
* 状態の保持 (カウンタ等) | |||
* 関数ファクトリ (設定値を持つ関数の生成) | |||
* デコレータの実装 | |||
* コールバック関数のカスタマイズ | |||
<br><br> | <br><br> | ||
== | == 関数内関数とクロージャの違い == | ||
まず、クロージャを理解するために、関数内関数を見ておく。<br> | まず、クロージャを理解するために、関数内関数を見ておく。<br> | ||
<br> | |||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
# 関数内関数 | |||
def OuterFunc(a, b): | def OuterFunc(a, b): | ||
def InnerFunc(): | |||
return a + b | |||
return InnerFunc() | |||
print(OuterFunc(2, 3)) | print(OuterFunc(2, 3)) | ||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
5 | 5 | ||
<br> | <br> | ||
クロージャは、上記のサンプルコードを次のように変更する。<br> | クロージャは、上記のサンプルコードを次のように変更する。<br> | ||
<br> | |||
以下の例では、OuterFunc関数の戻り値が、InnerFunc関数を呼び出すのではなく、丸括弧を使用せずにInnerFuncオブジェクトを記述している。<br> | 以下の例では、OuterFunc関数の戻り値が、InnerFunc関数を呼び出すのではなく、丸括弧を使用せずにInnerFuncオブジェクトを記述している。<br> | ||
このサンプルコードを実行すると、InnerFuncオブジェクトのアドレスが返される。<br> | このサンプルコードを実行すると、InnerFuncオブジェクトのアドレスが返される。<br> | ||
<br> | <br> | ||
これは、まだInnerFunc関数が実行されていない状態である。<br> | これは、まだInnerFunc関数が実行されていない状態である。<br> | ||
この状態を記憶して、後で使用することができるものがクロージャである。<br> | |||
ここでは、InnerFuncがクロージャになる。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
def OuterFunc(a, b): | def OuterFunc(a, b): | ||
def InnerFunc(): | |||
return a + b | |||
return InnerFunc | |||
print(OuterFunc(2, 3)) | print(OuterFunc(2, 3)) | ||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
<function outer_function.<locals>.inner_function at 0x10d86b7b8> | |||
<function outer_function.<locals>.inner_function at 0x10d86b7b8 | |||
<br><br> | <br><br> | ||
== クロージャの実行 == | == クロージャの実行 == | ||
クロージャを実行するには、次のように行う。<br> | クロージャを実行するには、次のように行う。<br> | ||
<br> | |||
以下の例では、OuterFunc関数をfuncオブジェクトに代入している。<br> | |||
これに、丸括弧()を付けてfuncオブジェクトを実行している。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
def OuterFunc(a, b): | def OuterFunc(a, b): | ||
def InnerFunc(): | |||
return a + b | |||
return InnerFunc | |||
func = OuterFunc(2, 3) | func = OuterFunc(2, 3) | ||
print(func()) | print(func()) | ||
# | </syntaxhighlight> | ||
<br> | |||
# 実行例 : | |||
5 | 5 | ||
<br> | <br> | ||
次に、長方形の面積を計算するサンプルコードを記述する。 | 次に、長方形の面積を計算するサンプルコードを記述する。<br> | ||
<br> | |||
以下の例では、引数に横(width)を与えるAreaCalc関数を定義して、その中に縦(height)を引数を定義して面積を計算するAreaCalcFunc関数を定義している。<br> | 以下の例では、引数に横(width)を与えるAreaCalc関数を定義して、その中に縦(height)を引数を定義して面積を計算するAreaCalcFunc関数を定義している。<br> | ||
戻り値は、丸括弧() | 戻り値は、丸括弧()を外したAreaCalcFuncオブジェクトを返す。(クロージャになっている)<br> | ||
<br> | <br> | ||
まず、2種類の横の数値を与えた後、オブジェクト変数ac1とac2にAreaCalcFuncオブジェクトを代入している。<br> | まず、2種類の横の数値を与えた後、オブジェクト変数ac1とac2にAreaCalcFuncオブジェクトを代入している。<br> | ||
このオブジェクト変数ac1とac2に丸括弧()を付けて縦の数値を与えることで計算が実行される。<br> | このオブジェクト変数ac1とac2に丸括弧()を付けて縦の数値を与えることで計算が実行される。<br> | ||
<br> | |||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
def AreaCalc(width): | def AreaCalc(width): | ||
def AreaCalcFunc(height): | |||
return width * height | |||
return AreaCalcFunc | |||
# widthが25と50の場合を計算 | # widthが25と50の場合を計算 | ||
ac1 = AreaCalc(25) | ac1 = AreaCalc(25) | ||
ac2 = AreaCalc(50) | ac2 = AreaCalc(50) | ||
# heightを10として面積を求める | # heightを10として面積を求める | ||
print(ac1(10)) | print(ac1(10)) | ||
print(ac2(10)) | print(ac2(10)) | ||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
250 | 250 | ||
500 | 500 | ||
<br><br> | <br><br> | ||
== クロージャの仕組み (自由変数とスコープ) == | |||
クロージャを理解するには、Pythonの <u>スコープルール (LEGBルール)</u> と <u>自由変数</u> の概念を知る必要がある。<br> | |||
<br> | |||
==== LEGBルール ==== | |||
Pythonは変数を以下の順序で探索する。<br> | |||
* L (Local) | |||
*: ローカルスコープ (関数内) | |||
* E (Enclosing) | |||
*: 外側の関数のスコープ | |||
* G (Global) | |||
*: グローバルスコープ (モジュールレベル) | |||
* B (Built-in) | |||
*: 組み込みスコープ (len, print等) | |||
<br> | |||
==== 束縛変数と自由変数 ==== | |||
* 束縛変数 | |||
*: 関数内で定義された変数 | |||
* 自由変数 | |||
*: 関数内で使用されているが、その関数内で定義されていない変数 (外側のスコープから取得) | |||
<br> | |||
以下の例では、InnerFunc内のxは自由変数である。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | |||
def OuterFunc(x): | |||
# xはOuterFuncの束縛変数 | |||
def InnerFunc(y): | |||
# yはInnerFuncの束縛変数 | |||
# xはInnerFuncの自由変数 (外側のスコープから取得) | |||
return x + y | |||
return InnerFunc | |||
closure = OuterFunc(10) | |||
print(closure(5)) | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
15 | |||
<br> | |||
クロージャは、自由変数の値を関数オブジェクト内に保持する。<br> | |||
OuterFunc関数の実行が終わった後も、InnerFunc関数はxの値 (10) を記憶している。<br> | |||
<br><br> | |||
== nonlocalキーワード == | |||
クロージャで外側のスコープの変数を変更するには、<code>nonlocal</code> キーワードを使用する。<br> | |||
<br> | |||
==== 参照と代入の違い ==== | |||
外側のスコープの変数を参照するだけなら、nonlocalは不要である。<br> | |||
しかし、代入を行うと、新しいローカル変数が作成されてしまう。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | |||
def Counter(): | |||
count = 0 | |||
def Increment(): | |||
# これはエラーになる (countに代入する前に参照している) | |||
count = count + 1 | |||
return count | |||
return Increment | |||
# UnboundLocalError: local variable 'count' referenced before assignment | |||
</syntaxhighlight> | |||
<br> | |||
==== nonlocalを使用した正しい実装 ==== | |||
<code>nonlocal</code> キーワードを使用すると、外側のスコープの変数を変更できる。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | |||
def Counter(): | |||
count = 0 | |||
def Increment(): | |||
nonlocal count | |||
count = count + 1 | |||
return count | |||
return Increment | |||
counter = Counter() | |||
print(counter()) | |||
print(counter()) | |||
print(counter()) | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
1 | |||
2 | |||
3 | |||
<br> | |||
以下の例では、nonlocalを使用して複数の操作を提供するカウンタを実装している。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | |||
def CounterWithReset(): | |||
count = 0 | |||
def Increment(): | |||
nonlocal count | |||
count += 1 | |||
return count | |||
def Decrement(): | |||
nonlocal count | |||
count -= 1 | |||
return count | |||
def Reset(): | |||
nonlocal count | |||
count = 0 | |||
return count | |||
def GetValue(): | |||
return count | |||
return Increment, Decrement, Reset, GetValue | |||
inc, dec, reset, get = CounterWithReset() | |||
print(inc()) | |||
print(inc()) | |||
print(dec()) | |||
print(get()) | |||
print(reset()) | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
1 | |||
2 | |||
1 | |||
1 | |||
0 | |||
<br><br> | |||
== クロージャの実践的な使用例 == | |||
クロージャは、状態を保持する関数や、特定の設定値を持つ関数を生成する時に便利である。<br> | |||
<br> | |||
==== カウンター (状態保持) ==== | |||
以下の例では、カウンターの状態をクロージャで保持している。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | |||
def MakeCounter(): | |||
count = 0 | |||
def Counter(): | |||
nonlocal count | |||
count += 1 | |||
return count | |||
return Counter | |||
counter1 = MakeCounter() | |||
counter2 = MakeCounter() | |||
print(counter1()) | |||
print(counter1()) | |||
print(counter2()) | |||
print(counter1()) | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
1 | |||
2 | |||
1 | |||
3 | |||
<br> | |||
==== 関数ファクトリ (乗算器) ==== | |||
以下の例では、特定の係数で乗算する関数を生成している。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | |||
def MakeMultiplier(factor): | |||
def Multiply(x): | |||
return x * factor | |||
return Multiply | |||
times2 = MakeMultiplier(2) | |||
times5 = MakeMultiplier(5) | |||
times10 = MakeMultiplier(10) | |||
print(times2(3)) | |||
print(times5(3)) | |||
print(times10(3)) | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
6 | |||
15 | |||
30 | |||
<br> | |||
==== 設定値のラッピング ==== | |||
以下の例では、デフォルト設定値を持つ関数を生成している。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | |||
def MakeGreeter(greeting): | |||
def Greet(name): | |||
return f"{greeting}, {name}!" | |||
return Greet | |||
english_greeter = MakeGreeter("Hello") | |||
japanese_greeter = MakeGreeter("こんにちは") | |||
french_greeter = MakeGreeter("Bonjour") | |||
print(english_greeter("Alice")) | |||
print(japanese_greeter("太郎")) | |||
print(french_greeter("Marie")) | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
Hello, Alice! | |||
こんにちは, 太郎! | |||
Bonjour, Marie! | |||
<br><br> | |||
== クロージャとデコレータ == | |||
デコレータは、クロージャの代表的な応用例である。<br> | |||
デコレータは、関数を受け取って、その関数を拡張した新しい関数を返す。<br> | |||
<br> | |||
==== 基本的なデコレータ ==== | |||
以下の例では、関数の実行時間を計測するデコレータを実装している。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | |||
import time | |||
def TimerDecorator(func): | |||
def Wrapper(*args, **kwargs): | |||
start = time.time() | |||
result = func(*args, **kwargs) | |||
end = time.time() | |||
print(f"{func.__name__}の実行時間: {end - start:.4f}秒") | |||
return result | |||
return Wrapper | |||
@TimerDecorator | |||
def SlowFunction(): | |||
time.sleep(1) | |||
return "完了" | |||
result = SlowFunction() | |||
print(result) | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
SlowFunctionの実行時間: 1.0012秒 | |||
完了 | |||
<br> | |||
==== functools.wrapsの重要性 ==== | |||
デコレータを実装する場合、functools.wrapsを使用して元の関数のメタデータを保持する。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | |||
from functools import wraps | |||
import time | |||
def TimerDecorator(func): | |||
@wraps(func) | |||
def Wrapper(*args, **kwargs): | |||
start = time.time() | |||
result = func(*args, **kwargs) | |||
end = time.time() | |||
print(f"{func.__name__}の実行時間: {end - start:.4f}秒") | |||
return result | |||
return Wrapper | |||
@TimerDecorator | |||
def Calculate(x, y): | |||
"""2つの数値を加算する""" | |||
return x + y | |||
# functools.wrapsを使用すると、元の関数名とドキュメントが保持される | |||
print(Calculate.__name__) | |||
print(Calculate.__doc__) | |||
print(Calculate(3, 5)) | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
Calculate | |||
2つの数値を加算する | |||
Calculateの実行時間: 0.0000秒 | |||
8 | |||
<br> | |||
==== 引数付きデコレータ ==== | |||
以下の例では、引数を受け取るデコレータを定義している。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | |||
def Repeat(times): | |||
def Decorator(func): | |||
def Wrapper(*args, **kwargs): | |||
for _ in range(times): | |||
result = func(*args, **kwargs) | |||
return result | |||
return Wrapper | |||
return Decorator | |||
@Repeat(3) | |||
def Greet(name): | |||
print(f"Hello, {name}!") | |||
Greet("Alice") | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
Hello, Alice! | |||
Hello, Alice! | |||
Hello, Alice! | |||
<br><br> | |||
== クロージャを使用する時の注意 == | |||
==== ループ内でのクロージャ問題 ==== | |||
ループ内でクロージャを作成すると、予期しない動作をする場合がある。<br> | |||
<br> | |||
以下の例では、クロージャがiの参照を保持しているため、ループ終了時のiの値 (2) が使用されてしまう。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | |||
# 間違った実装 | |||
functions = [] | |||
for i in range(3): | |||
def func(): | |||
return i | |||
functions.append(func) | |||
# すべて2を返す (期待: 0, 1, 2) | |||
for f in functions: | |||
print(f()) | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
2 | |||
2 | |||
2 | |||
<br> | |||
==== 解決方法 1 : デフォルト引数を使用する ==== | |||
デフォルト引数を使用すると、ループの各反復時の値をキャプチャできる。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | |||
functions = [] | |||
for i in range(3): | |||
def func(x=i): | |||
return x | |||
functions.append(func) | |||
for f in functions: | |||
print(f()) | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
0 | |||
1 | |||
2 | |||
<br> | |||
==== 解決方法 2 : 関数ファクトリを使用する ==== | |||
外側の関数でパラメータをキャプチャする。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | |||
def MakeFunc(i): | |||
def func(): | |||
return i | |||
return func | |||
functions = [] | |||
for i in range(3): | |||
functions.append(MakeFunc(i)) | |||
for f in functions: | |||
print(f()) | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
0 | |||
1 | |||
2 | |||
<br> | |||
==== 解決方法 3 : ラムダ式とデフォルト引数 ==== | |||
ラムダ式でもデフォルト引数を使用できる。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | |||
functions = [lambda x=i: x for i in range(3)] | |||
for f in functions: | |||
print(f()) | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
0 | |||
1 | |||
2 | |||
<br><br> | |||
== クロージャとラムダ式 == | |||
ラムダ式でもクロージャを形成できる。<br> | |||
<br> | |||
==== ラムダ式によるクロージャ ==== | |||
以下の例では、ラムダ式を使用してクロージャを作成している。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | |||
def MakeAdder(x): | |||
return lambda y: x + y | |||
add5 = MakeAdder(5) | |||
add10 = MakeAdder(10) | |||
print(add5(3)) | |||
print(add10(3)) | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
8 | |||
13 | |||
<br> | |||
==== ラムダ式とdefの比較 ==== | |||
以下の2つの実装は同じ動作をする。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | |||
# def文を使用 | |||
def MakeMultiplier_def(factor): | |||
def Multiply(x): | |||
return x * factor | |||
return Multiply | |||
# ラムダ式を使用 | |||
def MakeMultiplier_lambda(factor): | |||
return lambda x: x * factor | |||
times3_def = MakeMultiplier_def(3) | |||
times3_lambda = MakeMultiplier_lambda(3) | |||
print(times3_def(7)) | |||
print(times3_lambda(7)) | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
21 | |||
21 | |||
<br> | |||
ラムダ式は簡潔だが、複雑なロジックにはdef文を使用する方が読みやすい。<br> | |||
<br><br> | |||
== __closure__属性 == | |||
クロージャの内部構造は、<code>__closure__</code> 属性を使用して検査できる。<br> | |||
<br> | |||
==== クロージャの内部を確認する ==== | |||
以下の例では、クロージャが保持している自由変数を確認している。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | |||
def OuterFunc(x, y): | |||
def InnerFunc(z): | |||
return x + y + z | |||
return InnerFunc | |||
closure = OuterFunc(10, 20) | |||
# クロージャの情報を確認 | |||
print(f"クロージャ: {closure.__closure__}") | |||
print(f"セルの数: {len(closure.__closure__)}") | |||
# 各セルの値を確認 | |||
for i, cell in enumerate(closure.__closure__): | |||
print(f"セル{i}: {cell.cell_contents}") | |||
print(f"実行結果: {closure(5)}") | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
クロージャ: (<cell at 0x...: int object at 0x...>, <cell at 0x...: int object at 0x...>) | |||
セルの数: 2 | |||
セル0: 10 | |||
セル1: 20 | |||
実行結果: 35 | |||
<br> | |||
==== クロージャでない関数 ==== | |||
自由変数を持たない関数は、<code>__closure__</code> が <code>None</code> になる。<br> | |||
<br> | |||
<syntaxhighlight lang="python"> | |||
def SimpleFunc(x): | |||
return x * 2 | |||
print(f"クロージャ: {SimpleFunc.__closure__}") | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
クロージャ: None | |||
<br><br> | |||
== クロージャとクラスの比較 == | |||
クロージャとクラスは、どちらも状態を保持できる。<br> | |||
どちらを使用するかは、用途によって決める。<br> | |||
<br> | |||
==== クロージャ版のカウンタ ==== | |||
<syntaxhighlight lang="python"> | |||
def MakeCounter(): | |||
count = 0 | |||
def Increment(): | |||
nonlocal count | |||
count += 1 | |||
return count | |||
def GetValue(): | |||
return count | |||
return Increment, GetValue | |||
increment, get_value = MakeCounter() | |||
print(increment()) | |||
print(increment()) | |||
print(get_value()) | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
1 | |||
2 | |||
2 | |||
<br> | |||
==== クラス版のカウンタ ==== | |||
<syntaxhighlight lang="python"> | |||
class Counter: | |||
def __init__(self): | |||
self.count = 0 | |||
def Increment(self): | |||
self.count += 1 | |||
return self.count | |||
def GetValue(self): | |||
return self.count | |||
counter = Counter() | |||
print(counter.Increment()) | |||
print(counter.Increment()) | |||
print(counter.GetValue()) | |||
</syntaxhighlight> | |||
<br> | |||
# 実行例 : | |||
1 | |||
2 | |||
2 | |||
<br> | |||
==== クロージャとクラスの使い分け ==== | |||
* クロージャを使用すべき場合 | |||
*: 単純な状態保持 (1〜2個の変数) | |||
*: 関数ファクトリ (特定の設定値を持つ関数の生成) | |||
*: デコレータの実装 | |||
*: 一時的なコールバック関数 | |||
*: <br> | |||
* クラスを使用すべき場合 | |||
*: 複数の状態変数と複数のメソッド | |||
*: 継承が必要な場合 | |||
*: 明確なインターフェースが必要な場合 | |||
*: 状態の可視性が重要な場合 (self.count等) | |||
<br><br> | |||
{{#seo: | |||
|title={{PAGENAME}} : Exploring Electronics and SUSE Linux | MochiuWiki | |||
|keywords=MochiuWiki,Mochiu,Wiki,Mochiu Wiki,Electric Circuit,Electric,pcb,Mathematics,AVR,TI,STMicro,AVR,ATmega,MSP430,STM,Arduino,Xilinx,FPGA,Verilog,HDL,PinePhone,Pine Phone,Raspberry,Raspberry Pi,C,C++,C#,Qt,Qml,MFC,Shell,Bash,Zsh,Fish,SUSE,SLE,Suse Enterprise,Suse Linux,openSUSE,open SUSE,Leap,Linux,uCLnux,Python,クロージャ,closure,関数,function,スコープ,scope,nonlocal,デコレータ,decorator,ラムダ式,lambda,電気回路,電子回路,基板,プリント基板 | |||
|description={{PAGENAME}} - 電子回路とSUSE Linuxに関する情報 | This page is {{PAGENAME}} in our wiki about electronic circuits and SUSE Linux | |||
|image=/resources/assets/MochiuLogo_Single_Blue.png | |||
}} | |||
__FORCETOC__ | __FORCETOC__ | ||
[[カテゴリ:Python]] | [[カテゴリ:Python]] | ||
2026年2月1日 (日) 06:12時点における最新版
概要
クロージャとは、参照環境を伴った関数、あるいはその関数への参照のことを指す。
クロージャは、関数内関数やC / C++ / C#の関数オブジェクトに似ている。
関数内の変数を扱う時、その関数が宣言された時のスコープによって実行されるというような説明もできる。
クロージャは、外側の関数の変数を「記憶」して、後から実行することができる強力な機能である。
主な用途:
- 状態の保持 (カウンタ等)
- 関数ファクトリ (設定値を持つ関数の生成)
- デコレータの実装
- コールバック関数のカスタマイズ
関数内関数とクロージャの違い
まず、クロージャを理解するために、関数内関数を見ておく。
# 関数内関数
def OuterFunc(a, b):
def InnerFunc():
return a + b
return InnerFunc()
print(OuterFunc(2, 3))
# 実行例 : 5
クロージャは、上記のサンプルコードを次のように変更する。
以下の例では、OuterFunc関数の戻り値が、InnerFunc関数を呼び出すのではなく、丸括弧を使用せずにInnerFuncオブジェクトを記述している。
このサンプルコードを実行すると、InnerFuncオブジェクトのアドレスが返される。
これは、まだInnerFunc関数が実行されていない状態である。
この状態を記憶して、後で使用することができるものがクロージャである。
ここでは、InnerFuncがクロージャになる。
def OuterFunc(a, b):
def InnerFunc():
return a + b
return InnerFunc
print(OuterFunc(2, 3))
# 実行例 : <function outer_function.<locals>.inner_function at 0x10d86b7b8>
クロージャの実行
クロージャを実行するには、次のように行う。
以下の例では、OuterFunc関数をfuncオブジェクトに代入している。
これに、丸括弧()を付けてfuncオブジェクトを実行している。
def OuterFunc(a, b):
def InnerFunc():
return a + b
return InnerFunc
func = OuterFunc(2, 3)
print(func())
# 実行例 : 5
次に、長方形の面積を計算するサンプルコードを記述する。
以下の例では、引数に横(width)を与えるAreaCalc関数を定義して、その中に縦(height)を引数を定義して面積を計算するAreaCalcFunc関数を定義している。
戻り値は、丸括弧()を外したAreaCalcFuncオブジェクトを返す。(クロージャになっている)
まず、2種類の横の数値を与えた後、オブジェクト変数ac1とac2にAreaCalcFuncオブジェクトを代入している。
このオブジェクト変数ac1とac2に丸括弧()を付けて縦の数値を与えることで計算が実行される。
def AreaCalc(width):
def AreaCalcFunc(height):
return width * height
return AreaCalcFunc
# widthが25と50の場合を計算
ac1 = AreaCalc(25)
ac2 = AreaCalc(50)
# heightを10として面積を求める
print(ac1(10))
print(ac2(10))
# 実行例 : 250 500
クロージャの仕組み (自由変数とスコープ)
クロージャを理解するには、Pythonの スコープルール (LEGBルール) と 自由変数 の概念を知る必要がある。
LEGBルール
Pythonは変数を以下の順序で探索する。
- L (Local)
- ローカルスコープ (関数内)
- E (Enclosing)
- 外側の関数のスコープ
- G (Global)
- グローバルスコープ (モジュールレベル)
- B (Built-in)
- 組み込みスコープ (len, print等)
束縛変数と自由変数
- 束縛変数
- 関数内で定義された変数
- 自由変数
- 関数内で使用されているが、その関数内で定義されていない変数 (外側のスコープから取得)
以下の例では、InnerFunc内のxは自由変数である。
def OuterFunc(x):
# xはOuterFuncの束縛変数
def InnerFunc(y):
# yはInnerFuncの束縛変数
# xはInnerFuncの自由変数 (外側のスコープから取得)
return x + y
return InnerFunc
closure = OuterFunc(10)
print(closure(5))
# 実行例 : 15
クロージャは、自由変数の値を関数オブジェクト内に保持する。
OuterFunc関数の実行が終わった後も、InnerFunc関数はxの値 (10) を記憶している。
nonlocalキーワード
クロージャで外側のスコープの変数を変更するには、nonlocal キーワードを使用する。
参照と代入の違い
外側のスコープの変数を参照するだけなら、nonlocalは不要である。
しかし、代入を行うと、新しいローカル変数が作成されてしまう。
def Counter():
count = 0
def Increment():
# これはエラーになる (countに代入する前に参照している)
count = count + 1
return count
return Increment
# UnboundLocalError: local variable 'count' referenced before assignment
nonlocalを使用した正しい実装
nonlocal キーワードを使用すると、外側のスコープの変数を変更できる。
def Counter():
count = 0
def Increment():
nonlocal count
count = count + 1
return count
return Increment
counter = Counter()
print(counter())
print(counter())
print(counter())
# 実行例 : 1 2 3
以下の例では、nonlocalを使用して複数の操作を提供するカウンタを実装している。
def CounterWithReset():
count = 0
def Increment():
nonlocal count
count += 1
return count
def Decrement():
nonlocal count
count -= 1
return count
def Reset():
nonlocal count
count = 0
return count
def GetValue():
return count
return Increment, Decrement, Reset, GetValue
inc, dec, reset, get = CounterWithReset()
print(inc())
print(inc())
print(dec())
print(get())
print(reset())
# 実行例 : 1 2 1 1 0
クロージャの実践的な使用例
クロージャは、状態を保持する関数や、特定の設定値を持つ関数を生成する時に便利である。
カウンター (状態保持)
以下の例では、カウンターの状態をクロージャで保持している。
def MakeCounter():
count = 0
def Counter():
nonlocal count
count += 1
return count
return Counter
counter1 = MakeCounter()
counter2 = MakeCounter()
print(counter1())
print(counter1())
print(counter2())
print(counter1())
# 実行例 : 1 2 1 3
関数ファクトリ (乗算器)
以下の例では、特定の係数で乗算する関数を生成している。
def MakeMultiplier(factor):
def Multiply(x):
return x * factor
return Multiply
times2 = MakeMultiplier(2)
times5 = MakeMultiplier(5)
times10 = MakeMultiplier(10)
print(times2(3))
print(times5(3))
print(times10(3))
# 実行例 : 6 15 30
設定値のラッピング
以下の例では、デフォルト設定値を持つ関数を生成している。
def MakeGreeter(greeting):
def Greet(name):
return f"{greeting}, {name}!"
return Greet
english_greeter = MakeGreeter("Hello")
japanese_greeter = MakeGreeter("こんにちは")
french_greeter = MakeGreeter("Bonjour")
print(english_greeter("Alice"))
print(japanese_greeter("太郎"))
print(french_greeter("Marie"))
# 実行例 : Hello, Alice! こんにちは, 太郎! Bonjour, Marie!
クロージャとデコレータ
デコレータは、クロージャの代表的な応用例である。
デコレータは、関数を受け取って、その関数を拡張した新しい関数を返す。
基本的なデコレータ
以下の例では、関数の実行時間を計測するデコレータを実装している。
import time
def TimerDecorator(func):
def Wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__}の実行時間: {end - start:.4f}秒")
return result
return Wrapper
@TimerDecorator
def SlowFunction():
time.sleep(1)
return "完了"
result = SlowFunction()
print(result)
# 実行例 : SlowFunctionの実行時間: 1.0012秒 完了
functools.wrapsの重要性
デコレータを実装する場合、functools.wrapsを使用して元の関数のメタデータを保持する。
from functools import wraps
import time
def TimerDecorator(func):
@wraps(func)
def Wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
end = time.time()
print(f"{func.__name__}の実行時間: {end - start:.4f}秒")
return result
return Wrapper
@TimerDecorator
def Calculate(x, y):
"""2つの数値を加算する"""
return x + y
# functools.wrapsを使用すると、元の関数名とドキュメントが保持される
print(Calculate.__name__)
print(Calculate.__doc__)
print(Calculate(3, 5))
# 実行例 : Calculate 2つの数値を加算する Calculateの実行時間: 0.0000秒 8
引数付きデコレータ
以下の例では、引数を受け取るデコレータを定義している。
def Repeat(times):
def Decorator(func):
def Wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return Wrapper
return Decorator
@Repeat(3)
def Greet(name):
print(f"Hello, {name}!")
Greet("Alice")
# 実行例 : Hello, Alice! Hello, Alice! Hello, Alice!
クロージャを使用する時の注意
ループ内でのクロージャ問題
ループ内でクロージャを作成すると、予期しない動作をする場合がある。
以下の例では、クロージャがiの参照を保持しているため、ループ終了時のiの値 (2) が使用されてしまう。
# 間違った実装
functions = []
for i in range(3):
def func():
return i
functions.append(func)
# すべて2を返す (期待: 0, 1, 2)
for f in functions:
print(f())
# 実行例 : 2 2 2
解決方法 1 : デフォルト引数を使用する
デフォルト引数を使用すると、ループの各反復時の値をキャプチャできる。
functions = []
for i in range(3):
def func(x=i):
return x
functions.append(func)
for f in functions:
print(f())
# 実行例 : 0 1 2
解決方法 2 : 関数ファクトリを使用する
外側の関数でパラメータをキャプチャする。
def MakeFunc(i):
def func():
return i
return func
functions = []
for i in range(3):
functions.append(MakeFunc(i))
for f in functions:
print(f())
# 実行例 : 0 1 2
解決方法 3 : ラムダ式とデフォルト引数
ラムダ式でもデフォルト引数を使用できる。
functions = [lambda x=i: x for i in range(3)]
for f in functions:
print(f())
# 実行例 : 0 1 2
クロージャとラムダ式
ラムダ式でもクロージャを形成できる。
ラムダ式によるクロージャ
以下の例では、ラムダ式を使用してクロージャを作成している。
def MakeAdder(x):
return lambda y: x + y
add5 = MakeAdder(5)
add10 = MakeAdder(10)
print(add5(3))
print(add10(3))
# 実行例 : 8 13
ラムダ式とdefの比較
以下の2つの実装は同じ動作をする。
# def文を使用
def MakeMultiplier_def(factor):
def Multiply(x):
return x * factor
return Multiply
# ラムダ式を使用
def MakeMultiplier_lambda(factor):
return lambda x: x * factor
times3_def = MakeMultiplier_def(3)
times3_lambda = MakeMultiplier_lambda(3)
print(times3_def(7))
print(times3_lambda(7))
# 実行例 : 21 21
ラムダ式は簡潔だが、複雑なロジックにはdef文を使用する方が読みやすい。
__closure__属性
クロージャの内部構造は、__closure__ 属性を使用して検査できる。
クロージャの内部を確認する
以下の例では、クロージャが保持している自由変数を確認している。
def OuterFunc(x, y):
def InnerFunc(z):
return x + y + z
return InnerFunc
closure = OuterFunc(10, 20)
# クロージャの情報を確認
print(f"クロージャ: {closure.__closure__}")
print(f"セルの数: {len(closure.__closure__)}")
# 各セルの値を確認
for i, cell in enumerate(closure.__closure__):
print(f"セル{i}: {cell.cell_contents}")
print(f"実行結果: {closure(5)}")
# 実行例 : クロージャ: (<cell at 0x...: int object at 0x...>, <cell at 0x...: int object at 0x...>) セルの数: 2 セル0: 10 セル1: 20 実行結果: 35
クロージャでない関数
自由変数を持たない関数は、__closure__ が None になる。
def SimpleFunc(x):
return x * 2
print(f"クロージャ: {SimpleFunc.__closure__}")
# 実行例 : クロージャ: None
クロージャとクラスの比較
クロージャとクラスは、どちらも状態を保持できる。
どちらを使用するかは、用途によって決める。
クロージャ版のカウンタ
def MakeCounter():
count = 0
def Increment():
nonlocal count
count += 1
return count
def GetValue():
return count
return Increment, GetValue
increment, get_value = MakeCounter()
print(increment())
print(increment())
print(get_value())
# 実行例 : 1 2 2
クラス版のカウンタ
class Counter:
def __init__(self):
self.count = 0
def Increment(self):
self.count += 1
return self.count
def GetValue(self):
return self.count
counter = Counter()
print(counter.Increment())
print(counter.Increment())
print(counter.GetValue())
# 実行例 : 1 2 2
クロージャとクラスの使い分け
- クロージャを使用すべき場合
- 単純な状態保持 (1〜2個の変数)
- 関数ファクトリ (特定の設定値を持つ関数の生成)
- デコレータの実装
- 一時的なコールバック関数
- クラスを使用すべき場合
- 複数の状態変数と複数のメソッド
- 継承が必要な場合
- 明確なインターフェースが必要な場合
- 状態の可視性が重要な場合 (self.count等)