Pythonジェネレータとイテレータの内部動作と応用

例外処理の拡張制御

標準の try...except ブロックに elsefinally を組み合わせることで、制御フローを細かく設計できます。例外が発生しなかった場合にのみ実行される else と、処理の成否に関わらず必ず実行される finally は、リソース解放や状態保存に適しています。また、assert で開発時の条件検証を行い、raise で意図的に例外を送出することで、エラーハンドリングの柔軟性を高めます。

try:
    target_value = missing_variable
except NameError as err:
    print(f"変数未定義エラー: {err}")
else:
    print("例外なし、正常に続行")
finally:
    print("後処理および状態保存")

# 型検証と例外の意図的送出
assert isinstance(target_value, float), "浮動小数点数を期待します"
raise ValueError("閾値を超えたため処理を中断します")

反復処理の内部機構とイテレータプロトコル

for 文は、内部でイテレータプロトコルに従って動作しています。対象オブジェクトの __iter__() でイテレータを取得し、__next__() を逐次呼び出すことで要素を消費します。要素が枯渇すると StopIteration が発生し、ループは安全に終了します。この仕組みは while ループで完全に再現可能です。

config_data = {'host': '127.0.0.1', 'port': 443, 'mode': 'secure'}
iterator_proxy = config_data.__iter__()

while True:
    try:
        print(iterator_proxy.__next__())
    except StopIteration:
        break

順序アクセスと位置アクセスの特性比較

  • イテレーション方式: 汎用性が高く、順序付き・未排序のコレクションを問わず利用可能。ただし単方向のストリームとして動作するため、重複消費や逆方向アクセスは不可能です。
  • インデックス方式: 特定の位置を直接指定して繰り返しアクセスできます。一方、整数添字をサポートするデータ型に限定されるため、汎用的な反復処理には不向きです。

ジェネレータ関数と yield の挙動

関数体内に yield が存在する場合、その関数は呼び出された瞬間に生成器オブジェクトを返します。関数本体の実行は __next__() が呼び出された時点まで遅延され、yield に到達すると実行が一時停止し、値を呼び出し元に返却します。再度 __next__() が呼ばれると、直前の停止位置から処理が再開されます。

def task_scheduler():
    print("タスク初期化フェーズ")
    yield "準備完了"
    print("中間データ処理")
    yield "中間完了"

scheduler = task_scheduler()
print(scheduler.__next__())
print(scheduler.__next__())

range 相当のジェネレータ実装

可変引数とデフォルト値を活用し、開始値・終了値・ステップ幅を柔軟に受け取るジェネレータを構築できます。yield を使うことで、大量の数列をメモリ上に展開せず、オンデマンドで値を生成することが可能になります。

def numeric_range(start, limit=None, step=1):
    if limit is None:
        limit = start
        start = 0
    
    current = start
    while current < limit:
        yield current
        current += step

for val in numeric_range(6):
    print(val, end=" ")

外部からの値注入 (send())

yield は式としても動作するため、呼び出し元から send() メソッド経由で値を代入できます。これにより、生成器が双方向の通信チャネルとして機能します。最初の起動には値の代入がないため、必ず __next__() で初期化します。

def pipeline_worker(node_id):
    print(f"{node_id} が待機状態に入りました")
    while True:
        incoming_data = yield
        print(f"{node_id} がデータを処理中: {incoming_data}")

worker_node = pipeline_worker("Alpha")
worker_node.__next__()  # 初期化
worker_node.send("バッチ1")
worker_node.send("バッチ2")

yieldreturn の根本的な差異

  • yield: 実行コンテキストを保存し、複数回にわたって値を返却可能。外部からの入力も受け付ける。
  • return: 関数の実行を即時終了し、スタックフレームを破棄する。返却後は再度実行不能。

ジェネレータ式と遅延評価の利点

リスト内包表記 [] がメモリ上に全ての要素を展開するのに対し、ジェネレータ式 () は必要となる値のみを計算して返却する遅延評価を行います。大規模データ処理やパイプライン構築において、メモリ使用量を劇的に抑制できます。

source_data = [10, 20, 30, 40, 50, 60]
# リスト内包表記: 即時評価、メモリ全展開
full_list = [x * 2 for x in source_data if x > 20]

# ジェネレータ式: 遅延評価、イテレーション時にのみ計算
lazy_gen = (x * 2 for x in source_data if x > 20)

連鎖ジェネレータの評価タイミング

ジェネレータ式がループ内で再代入される場合、実際の要素消費時には最も外側のループ変数の最終値が適用されます。これはクロージャ的な変数バインディングと、ジェネレータの遅延評価が組み合わさった結果です。

def apply_offset(base, item):
    return base + item

def raw_sequence():
    for idx in range(4):
        yield idx

seq_stream = raw_sequence()
for multiplier in [1, 10]:
    seq_stream = (apply_offset(multiplier, val) for val in seq_stream)

print(list(seq_stream))
# 出力: [21, 22, 23, 24]
# 解説: list() が評価された時点で multiplier は最終ループの 10 になる。
# 内側の 10 が適用され、さらに外側の 10 が加算される形となる。

頻出ビルトイン関数速習

  • abs(): 数値の絶対値を返却。
  • all() / any(): イテラブル内の要素が全て真/いずれかが真の場合に True を返す。
  • bin(), oct(), hex(): 数値をそれぞれ二進・八進・十六進文字列に変換。
  • bytes(), str(): 文字列とバイト列の相互変換。エンコード・デコード時に頻用。
  • callable(): 対象オブジェクトが呼び出し可能かどうかを真偽値で返す。
  • chr(), ord(): Unicodeコードポイントと文字の相互変換。
  • complex(): 実数から複素数オブジェクトを生成。
  • dir(): オブジェクトが保持する属性やメソッドの名前一覧を取得。
  • divmod(): 除算の商と剰余をタプルで返す。ページネーション計算で有用。
  • eval(), exec(): 文字列をPythonコードとして解釈・実行。セキュリティリスクに注意。
  • isinstance(): オブジェクトが指定クラスのインスタンスかどうかを検証。
  • pow(): 冪乗計算およびオプションのモジュロ計算を実行。
  • round(): 指定された小数点以下の桁数で四捨五入。
  • sum(): イテラブルの数値要素の合計値を算出。

タグ: Python ジェネレータ イテレータ 例外処理 ビルトイン関数

7月19日 23:52 投稿