「Pythonの基礎 - クロージャ」の版間の差分

提供: MochiuWiki : SUSE, EC, PCB

📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)

文字列「</source>」を「</syntaxhighlight>」に置換
編集の要約なし
 
1行目: 1行目:
== 概要 ==
== 概要 ==
Pythonには、クロージャというものが存在する。<br>
クロージャとは、参照環境を伴った関数、あるいはその関数への参照のことを指す。<br>
クロージャは、関数内関数やC / C++ / C#の関数オブジェクトに似ている。<br>
<br>
<br>
クロージャとは、参照環境を伴ったような関数、あるいはその関数への参照のことを指す。(関数内関数やC/C++/C#の関数オブジェクトに似ている)<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():
    def InnerFunc():
        return a + b
      return a + b
   
   
    return InnerFunc()
    return InnerFunc()
   
   
  print(OuterFunc(2, 3))
  print(OuterFunc(2, 3))
</syntaxhighlight>
<br>
# 実行例 :
   
   
# 出力
  5
  5
</syntaxhighlight>
<br>
<br>
クロージャは、上記のサンプルコードを次のように変更する。<br>
クロージャは、上記のサンプルコードを次のように変更する。<br>
<br>
以下の例では、OuterFunc関数の戻り値が、InnerFunc関数を呼び出すのではなく、丸括弧を使用せずにInnerFuncオブジェクトを記述している。<br>
以下の例では、OuterFunc関数の戻り値が、InnerFunc関数を呼び出すのではなく、丸括弧を使用せずにInnerFuncオブジェクトを記述している。<br>
このサンプルコードを実行すると、InnerFuncオブジェクトのアドレスが返される。<br>
このサンプルコードを実行すると、InnerFuncオブジェクトのアドレスが返される。<br>
<br>
<br>
これは、まだInnerFunc関数が実行されていない状態である。<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():
    def InnerFunc():
        return a + b
      return a + b
   
   
    return InnerFunc
    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>
</syntaxhighlight>
<br><br>
<br><br>


== クロージャの実行 ==
== クロージャの実行 ==
クロージャを実行するには、次のように行う。<br>
クロージャを実行するには、次のように行う。<br>
以下の例では、OuterFunc関数をfuncオブジェクトに代入している。これに、丸括弧()を付けてfuncオブジェクトを実行している。<br>
<br>
以下の例では、OuterFunc関数をfuncオブジェクトに代入している。<br>
これに、丸括弧()を付けてfuncオブジェクトを実行している。<br>
<br>
  <syntaxhighlight lang="python">
  <syntaxhighlight lang="python">
  def OuterFunc(a, b):
  def OuterFunc(a, b):
    def InnerFunc():
    def InnerFunc():
        return a + b
      return a + b
   
   
    return InnerFunc
    return InnerFunc
   
   
  func = OuterFunc(2, 3)
  func = OuterFunc(2, 3)
  print(func())
  print(func())
  # 出力
</syntaxhighlight>
<br>
  # 実行例 :
  5
  5
</syntaxhighlight>
<br>
<br>
次に、長方形の面積を計算するサンプルコードを記述する。(横(width)と縦(height)を乗算)<br>
次に、長方形の面積を計算するサンプルコードを記述する。<br>
<br>
以下の例では、引数に横(width)を与えるAreaCalc関数を定義して、その中に縦(height)を引数を定義して面積を計算するAreaCalcFunc関数を定義している。<br>
以下の例では、引数に横(width)を与えるAreaCalc関数を定義して、その中に縦(height)を引数を定義して面積を計算するAreaCalcFunc関数を定義している。<br>
戻り値は、丸括弧()を外したAareaCalcFuncオブジェクトを返す。(クロージャになっている)<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):
    def AreaCalcFunc(height):
        return width * height
      return width * height
   
   
    return AreaCalcFunc
    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>
== クロージャの仕組み (自由変数とスコープ) ==
クロージャを理解するには、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>
  </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>
<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等)