概要(クロージャは「外側の値を覚えた関数」をつくる仕組み)
クロージャは、関数の中で定義した内側関数が「外側の関数の変数」を覚えたまま動く仕組みです。外側関数の実行が終わっても、その値を保持し続けます。これにより「設定を閉じ込める」「状態を安全に持つ」など、シンプルで強力な小さな API を作れます。
def make_adder(step):
def add(x):
return x + step # 外側の step を覚えている
return add
plus10 = make_adder(10)
print(plus10(5)) # 15
Python基本構文と動き(ここが重要)
外側関数・内側関数・返す流れ
クロージャは「外側で値を受ける → 内側でその値を使う → 内側関数を返す」という流れで作ります。返された内側関数を、後から何度でも呼べます。
def make_rounder(ndigits):
def rounder(x):
return round(x, ndigits)
return rounder
r2 = make_rounder(2)
print(r2(3.14159)) # 3.14
Pythonスコープと参照(nonlocal の位置づけ)
内側関数は外側の「ローカル変数」を参照できます。もしその外側変数を「更新」したいなら nonlocal を使います。参照だけなら nonlocal は不要です。
def make_counter(start=0):
n = start
def inc():
nonlocal n
n += 1
return n
return inc
c = make_counter()
print(c()) # 1
print(c()) # 2
Pythonクロージャの強み(設定固定・状態保持・カプセル化)を深掘り
設定を固定して「短い呼び出し」にする
毎回同じパラメータを渡す代わりに、外側で一度だけ受けて閉じ込めます。以降の呼び出しは本質的な入力だけ。
def make_formatter(prefix="", suffix=""):
def fmt(x):
return f"{prefix}{x}{suffix}"
return fmt
info = make_formatter("[", "]")
print(info("OK")) # [OK]
Python状態を安全に持つ(グローバル変数の代替)
グローバル変数を書き換えるとバグの温床になります。クロージャは「関数が自分の状態を持つ」ので、外部から勝手に触らせない設計ができます。
def memoize(func):
cache = {}
def wrapper(x):
if x in cache:
return cache[x]
y = func(x)
cache[x] = y
return y
return wrapper
@memoize
def slow_square(n):
return n * n
Pythonカプセル化(外へ余計な名前を公開しない)
外側の関数専用のヘルパー処理は、内側関数に閉じ込めます。モジュール全体に余計な関数名を増やさず、意図を明確にできます。
def normalize(text):
def trim(s): return s.strip()
def lower(s): return s.casefold()
return lower(trim(text))
Python使いどころと設計の勘所(読みやすさ・テスト・パフォーマンス)
いつクロージャを選ぶか(def・クラスとの比較)
- 短い「設定固定」「小さな状態保持」ならクロージャが最短距離。
- 複雑な状態・複数メソッドが必要ならクラスが向いています。
- 公開関数で説明(docstring・型ヒント)が重要なら、トップレベルの def のほうが可読性が高いです。
テスト容易性のための形にする
「外側で設定 → 関数を返す」形は、その関数単体をユニットテストしやすいです。入力に対して決まった出力を返す設計に徹すると、テストが明快になります。
def make_threshold_filter(th):
def ok(x):
return x >= th
return ok
is_adult = make_threshold_filter(20)
assert is_adult(18) is False
assert is_adult(25) is True
Python定義コストとホットパス
外側関数を呼ぶたびに内側関数が定義されます。超高頻度の生成がボトルネックなら、事前に作って使い回す、またはトップレベルへ切り出す選択肢を検討しましょう。通常のスクリプトでは問題になりにくいです。
よくある落とし穴と対策(遅延束縛・nonlocalの濫用)
ループでの遅延束縛の罠(その時の値を固定する)
ループ内でラムダや内側関数を作ると「最後の値」を参照してしまうことがあります。デフォルト引数で束縛して、その時点の値を固定します。
funcs = []
for i in range(3):
def f(x, i=i): # i=i で今の i を閉じ込める
return x + i
funcs.append(f)
print([g(10) for g in funcs]) # [10, 11, 12]
Pythonnonlocal の使いすぎは読みにくさに直結
状態更新が増えてくると追跡が難しくなります。更新が多いならクラスへ移行し、責務を分けて管理してください。クロージャは「薄い状態」に留めると美しく保てます。
例題で身につける(定番から一歩先まで)
例題1:加算器(設定固定)
def make_adder(step):
def add(x):
return x + step
return add
add5 = make_adder(5)
print(add5(12)) # 17
Python例題2:カウンタ(状態保持)
def make_counter(start=0):
n = start
def inc():
nonlocal n
n += 1
return n
return inc
c = make_counter(10)
print(c(), c(), c()) # 11 12 13
Python例題3:検証関数ファクトリ(バリデーション)
def make_length_checker(min_len=1, max_len=100):
def check(s):
s = s.strip()
return min_len <= len(s) <= max_len
return check
ok = make_length_checker(3, 10)
print(ok("tea")) # True
print(ok(" ")) # False
Python例題4:軽いメモ化(キャッシュ)
def memoize(func):
cache = {}
def wrapper(x):
if x in cache:
return cache[x]
y = func(x)
cache[x] = y
return y
return wrapper
@memoize
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
print(fib(30)) # キャッシュありで高速化
Pythonまとめ
クロージャは「外側の値を覚えた関数」を作る仕組みで、設定固定・小さな状態保持・カプセル化に抜群の効果を発揮します。参照だけならそのまま、更新が必要なら nonlocal。ループでは遅延束縛に注意して「デフォルト引数で束縛」する。複雑化したらクラスへ——この線引きを身につければ、あなたのコードは短く、意図が明快で、再利用しやすくなります。
