Pythonにおける関数の第一級オブジェクト化とクロージャ・イテレータの仕組み

関数の第一級オブジェクトとしての性質

Pythonでは関数はとして扱われ、変数への代入や引数・戻り値として活用できます。関数オブジェクトの識別子を確認するには、関数名をprint()に渡します。

# 関数オブジェクトの識別子確認
def greet():
    print("hello")

print(greet)  # <function greet at 0x...>

変数への代入と間接呼び出し

def calculate():
    return 42

alias = calculate      # 関数名を変数に束縛
result = alias()       # alias経由で関数を実行
print(result)          # 42

高階関数としての利用

def multiplier(factor):
    def inner(value):
        return value * factor
    return inner         # 内部関数を返す

double = multiplier(2)
triple = multiplier(3)

print(double(5))       # 10
print(triple(5))       # 15

コレクションでの管理とディスパッチ

def action_a(): return "処理A"
def action_b(): return "処理B"
def action_c(): return "処理C"

handlers = [action_a, action_b, action_c]

for handler in handlers:
    print(handler())   # 各関数を順次実行

クロージャのメカニズム

クロージャとは、入れ子関数の内側関数が、外側関数のスコープにある変数を参照し、その変数が内側関数の実行時まで存続する仕組みです。ただし、参照する変数はグローバル変数であってはいけません。

クロージャの形成条件

def outer(x):
    def inner():
        # outerのローカル変数xを参照
        return x ** 2
    return inner

square = outer(10)     # outer実行後、xは本来消滅するが...
print(square())        # 100 — クロージャによりxが保持される

クロージャの検証方法

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

counter = create_counter(0)

# __closure__属性で確認(セルオブジェクトの有無)
print(counter.__closure__)           # (<cell ...>,)
print(counter.__closure__[0].cell_contents)  # 0

# 非クロージャの場合
def standalone():
    return 123

print(standalone.__closure__)        # None

多層クロージャの例

def level_one(a):
    def level_two(b):
        def level_three(c):
            return a + b + c
        return level_three
    return level_two

builder = level_one(1)
final = builder(2)
print(final(3))          # 6 — a=1, b=2, c=3が全て保持される

イテレータプロトコル

Pythonのイテレータは__iter__()と__next__()の両方を実装したオブジェクトです。__iter__のみを持つIterableと区別されます。

IterableとIteratorの違い

# dir()でメソッド確認
data = [1, 2, 3]
print('__iter__' in dir(data))      # True — Iterable
print('__next__' in dir(data))      # False — Iteratorではない

it = iter(data)                     # __iter__()でIterator生成
print('__next__' in dir(it))        # True — これがIterator

手動でのイテレーション

items = ['alpha', 'beta', 'gamma']
iterator = items.__iter__()

print(iterator.__next__())   # 'alpha'
print(iterator.__next__())   # 'beta'
print(iterator.__next__())   # 'gamma'
# 次回呼び出しで StopIteration 例外発生

Iterator生成の落とし穴

values = [10, 20, 30]

# 毎回新しいIteratorを生成しているため、先頭要素を繰り返す
print(values.__iter__().__next__())   # 10
print(values.__iter__().__next__())   # 10
print(values.__iter__().__next__())   # 10

# 正しい方法:同一Iteratorを保持
it = values.__iter__()
print(it.__next__())                  # 10
print(it.__next__())                  # 20

for文の内部動作

collection = ['x', 'y', 'z']

# 以下のfor文は...
for element in collection:
    print(element)

# 内部的には以下と等価
_idx = 0
_itr = collection.__iter__()
while _idx < len(collection):
    print(_itr.__next__())
    _idx += 1

型チェックの方法

from collections.abc import Iterable, Iterator

text = "sample"

print(isinstance(text, Iterable))           # True — 文字列はIterable
print(isinstance(text, Iterator))           # False — 文字列自体はIteratorでない

text_iter = iter(text)
print(isinstance(text_iter, Iterator))      # True — iter()適用後はIterator

イテレータの特性

  • 消費型:__next__()は前方のみ、一度読んだ要素には戻れない
  • 遅延評価:必要な時点まで値の生成を延期する
  • 状態保持:現在位置を内部に保持する
# 遅延評の実例:無限シーケンス
import itertools

def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

# 無限だが、必要な分だけ評価
fib = fibonacci()
for _ in range(5):
    print(next(fib))      # 0, 1, 1, 2, 3

タグ: Python 関数型プログラミング クロージャ イテレータプロトコル 高階関数

8月29日 03:44 投稿