概要(関数内関数は「外側の処理専用の小さな関数」をその場で持つための仕組み)
関数内関数(ネスト関数)は、ある関数の内部で別の関数を定義する書き方です。外側の関数にだけ関係する処理を内側に閉じ込めることで、見通しが良くなり、スコープ(見える範囲)を安全に保てます。さらに、外側の変数を内側の関数が覚え続ける「クロージャ」を作れて、状態や設定をきれいに保持できます。
def outer():
def inner():
print("inside")
inner() # 外側から内側を呼ぶ
outer()
Python基本構文と動き(ここが重要)
定義と呼び出しの流れを正しく押さえる
内側の関数は、外側の関数が実行されてはじめて定義されます。したがって、内側関数を使うには、外側の関数の中で呼ぶか、外側の関数の戻り値として内側関数を返してから使います。
def outer(x):
def inner(y):
return x + y # 外側の x を参照できる
return inner # 内側関数そのものを返す
add10 = outer(10) # x=10 を記憶した関数を取得
print(add10(3)) # 13
Pythonスコープの基本と「外側変数の参照」
内側関数は外側関数の変数を参照できます。これはレキシカルスコープ(定義位置で決まる見える範囲)の性質です。参照は自由ですが、書き換えたいときは nonlocal を使います。グローバル変数を書き換えるより安全で、影響範囲を最小化できます。
def make_counter():
n = 0
def inc():
nonlocal n
n += 1
return n
return inc
c = make_counter()
print(c()) # 1
print(c()) # 2
Python外側変数の保持(クロージャ)と設計の勘所
クロージャで「設定や状態」をきれいに運ぶ
外側の関数で設定値や初期状態を受け取り、内側関数がそれを覚えて動く。これがクロージャの本質です。APIのベースURL、丸め桁、重み係数などを閉じ込めると、毎回同じ引数を渡す必要がなくなります。
def make_rounder(ndigits):
def rounder(x):
return round(x, ndigits)
return rounder
r2 = make_rounder(2)
print(r2(3.14159)) # 3.14
Pythonnonlocal を使う場面と注意点
非破壊で足りるなら参照だけに留める設計が安全です。どうしても状態を更新したい場合だけ nonlocal を使います。なお、状態が複雑化してきたら、クラスへ移行するとテストと保守が楽になります。
関数内関数の使いどころ(カプセル化・デコレータ・小さなユーティリティ)
カプセル化で「外から触れさせない」補助処理にする
外側関数だけが使うヘルパーを内側に置くと、外へ余計な名前を公開せずに済みます。可読性が上がり、名前の衝突も避けられます。
def normalize(text):
def trim(s): return s.strip()
def tolower(s): return s.casefold()
return tolower(trim(text))
print(normalize(" Coffee "))
Pythonデコレータの基礎:前後に挟む処理を作る
関数を受け取って、内側関数で前後に共通処理を挟み込み、元の関数を呼ぶ形はデコレータの基本です。ログ、計測、キャッシュなどに応用できます。
def log_calls(func):
def wrapper(*args, **kwargs):
print(f"call {func.__name__} {args} {kwargs}")
result = func(*args, **kwargs)
print(f"done {func.__name__} -> {result}")
return result
return wrapper
@log_calls
def add(a, b):
return a + b
add(3, 5)
Python小さなユーティリティの「前設定」を作る
外側で既定値を受け取り、内側でそれを利用するファクトリは、使い心地の良いミニAPIになります。
def make_formatter(prefix="", suffix=""):
def fmt(x):
return f"{prefix}{x}{suffix}"
return fmt
f = make_formatter("[", "]")
print(f("OK")) # [OK]
Python重要ポイントの深掘り(読みやすさ・テスト・パフォーマンス)
読みやすさ最優先で「必要十分な内側関数」に留める
内側関数を増やしすぎたり、長くしすぎると、読みにくくなります。外側の責務に直接関係する補助処理だけを内側に閉じ込め、共通で再利用したい処理は def でトップレベルに切り出す判断が重要です。
テスト容易性のための戻り値設計
内側関数を返す形にすると、その関数単体をテストできます。状態を持つクロージャも、明確な入力と出力を設計すれば、ユニットテストで検証しやすくなります。
パフォーマンスと定義コスト
外側関数を呼ぶたびに内側関数は定義されます。高頻度で呼び出す場面では、定義を外に出すか、キャッシュする設計に切り替える選択肢を持ちましょう。多くのケースでは問題になりませんが、ホットパスでは意識しておくと安全です。
例題で身につける(定番から一歩先まで)
例題1:基本の関数内関数とスコープ参照
def outer(msg):
def inner():
print(msg) # 外側の msg を参照
inner()
outer("こんにちは")
Python例題2:クロージャによるカウンタ
def make_counter(start=0):
n = start
def inc():
nonlocal n
n += 1
return n
return inc
c1 = make_counter()
c2 = make_counter(10)
print(c1(), c1(), c2(), c2()) # 1 2 11 12
Python例題3:簡易キャッシュ(メモ化)
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
print(slow_square(10))
print(slow_square(10)) # キャッシュ命中
Python例題4:「前処理+後処理」を挟むラッパ
def with_trim(func):
def wrapper(s):
s = s.strip()
result = func(s)
return result
return wrapper
@with_trim
def shout(s):
return s.upper()
print(shout(" hello ")) # HELLO
Pythonまとめ
関数内関数は、外側の処理に密着した小さな関数をその場で持ち、スコープを安全に保つための道具です。外側の変数を参照・保持するクロージャを活用すると、設定や状態の取り回しが美しくなります。nonlocal は必要なときにだけ使い、読みやすさを最優先にして責務をきちんと区切ること。ヘルパーのカプセル化、デコレータによる共通処理の挟み込み、前設定ファクトリなど、あなたのコードを洗練させる「ちょうどいい使いどころ」を押さえれば、関数内関数は強力な味方になります。
