Python | 関数:関数内関数

Python
スポンサーリンク

概要(関数内関数は「外側の処理専用の小さな関数」をその場で持つための仕組み)

関数内関数(ネスト関数)は、ある関数の内部で別の関数を定義する書き方です。外側の関数にだけ関係する処理を内側に閉じ込めることで、見通しが良くなり、スコープ(見える範囲)を安全に保てます。さらに、外側の変数を内側の関数が覚え続ける「クロージャ」を作れて、状態や設定をきれいに保持できます。

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
Python

nonlocal を使う場面と注意点

非破壊で足りるなら参照だけに留める設計が安全です。どうしても状態を更新したい場合だけ 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 は必要なときにだけ使い、読みやすさを最優先にして責務をきちんと区切ること。ヘルパーのカプセル化、デコレータによる共通処理の挟み込み、前設定ファクトリなど、あなたのコードを洗練させる「ちょうどいい使いどころ」を押さえれば、関数内関数は強力な味方になります。

タイトルとURLをコピーしました