関数オブジェクト
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 ルールに従う:
- Local(現在の関数内)
- Enclosing(外側の関数)
- Global(モジュール全体)
- 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__ など)を保持できる。