Pythonにおける関数オブジェクト、ネスト、スコープ、デコレータの詳細解説

関数オブジェクト

Pythonでは関数は第一級オブジェクトとして扱われ、以下のようにデータとして操作可能である:

  • 変数に代入できる
  • 他の関数の引数として渡せる
  • 関数の戻り値として返せる
  • リストや辞書などのコンテナに格納できる

この性質を活かして、条件分岐をより簡潔に記述できる。例えば:

def handle_foo():
    print('foo')

def handle_bar():
    print('bar')

handlers = {
    'foo': handle_foo,
    'bar': handle_bar,
}

while True:
    cmd = input('>> ').strip()
    if cmd in handlers:
        handlers[cmd]()

関数のネスト

関数内で別の関数を呼び出す「ネスト呼び出し」や、関数内に関数を定義する「ネスト定義」が可能である。

例:複数の値から最大値を求める関数

def max2(a, b):
    return a if a > b else b

def max4(a, b, c, d):
    return max2(max2(max2(a, b), c), d)

print(max4(1, 2, 3, 4))  # 出力: 4

ネスト定義の例:

def outer():
    def inner():
        def core():
            print('from core')
        core()
    inner()

outer()  # 正常に実行され "from core" を出力
# core()  # エラー:グローバルスコープでは参照不可

内部で定義された関数は、その外側の関数スコープ内でのみ有効である。

名前空間とスコープ

名前空間(namespace)とは、識別子(変数名など)とその値の対応関係を保持する領域である。Pythonには以下の3種類がある:

  • 組込(built-in)名前空間:len, print など
  • グローバル(global)名前空間:モジュールレベルで定義された名前
  • ローカル(local)名前空間:関数内などで一時的に生成される名前

名前の解決順序は LEGB ルールに従う:

  1. Local(現在の関数内)
  2. Enclosing(外側の関数)
  3. Global(モジュール全体)
  4. Built-in(組込関数)

例:

max_val = 1

def f1():
    max_val = 2
    def f2():
        max_val = 3
        print(max_val)
    f2()

f1()          # 出力: 3
print(max_val) # 出力: 1

スコープは関数が定義された時点で決定され、呼び出し位置には影響されない。

x = 1
def make_func():
    def inner():
        print(x)
    return inner

x = 10000
func = make_func()
func()  # 出力: 10000(グローバル x を参照)

現在のスコープを確認するには globals()locals() を使用する。

global と nonlocal キーワード

global はグローバル変数を関数内で再代入する際に使用し、nonlocal は外側の関数スコープの変数を参照・変更するために用いる。

クロージャ

クロージャとは、内部関数が外側の関数のローカル変数を「閉じ込めて」保持する仕組みである。

def create_counter():
    count = 0
    def increment():
        nonlocal count
        current = count
        count += 1
        return current
    return increment

counter = create_counter()
print(counter())  # 0
print(counter())  # 1
print(counter())  # 2
print(counter.__closure__[0].cell_contents)  # 現在の count 値(3)

クロージャの主な用途は「遅延評価」や「状態の保持」である。例:

from urllib.request import urlopen

def make_fetcher(url):
    def fetch():
        return urlopen(url).read()
    return fetch

fetch_baidu = make_fetcher('http://www.baidu.com')
content = fetch_baidu().decode('utf-8')

このように、URL を事前に「包み込んで」おき、後で呼び出すことで柔軟な設計が可能になる。

デコレータ

デコレータはクロージャの応用であり、「オープン・クローズド原則(拡張には開いており、修正には閉じている)」を実現するためのパターンである。

基本的な無引数デコレータ

import time
from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f'実行時間: {end - start:.4f} 秒')
        return result
    return wrapper

@timer
def slow_task():
    time.sleep(1)
    print('タスク完了')

slow_task()

引数付きデコレータ

def authenticate(method='file'):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            user = input("ユーザー名: ")
            pwd = input("パスワード: ")
            if method == 'file':
                if user == 'admin' and pwd == 'secret':
                    print('認証成功')
                    return func(*args, **kwargs)
                else:
                    print('認証失敗')
            elif method == 'ldap':
                print('LDAP認証(未実装)')
        return wrapper
    return decorator

@authenticate(method='file')
def protected_action(name):
    print(f'実行: {name}')

protected_action('重要処理')

複数デコレータの適用

複数のデコレータをスタックすると、適用順序に注意が必要である:

def deco_a(func):
    print('deco_a 読み込み')
    def inner(*args, **kwargs):
        print('deco_a 実行')
        return func(*args, **kwargs)
    return inner

def deco_b(func):
    print('deco_b 読み込み')
    def inner(*args, **kwargs):
        print('deco_b 実行')
        return func(*args, **kwargs)
    return inner

def deco_c(func):
    print('deco_c 読み込み')
    def inner(*args, **kwargs):
        print('deco_c 実行')
        return func(*args, **kwargs)
    return inner

@deco_a
@deco_b
@deco_c
def target():
    print('本体関数')

print('--- 関数呼び出し ---')
target()

出力順:

  • 読み込み順:deco_c → deco_b → deco_a(下から上)
  • 実行順:deco_a → deco_b → deco_c → 本体(上から下)

@wraps(func) を使用することで、元の関数のメタ情報(__name__, __doc__ など)を保持できる。

タグ: Python 関数オブジェクト クロージャ デコレータ スコープ

8月9日 07:21 投稿