見出し画像

Python 3: Deep Dive (Part 2 - Iterators, Generators): クローズ、例外処理 (セクション13-5/14)

  • `yield from`を使用することで、サブジェネレータへの委譲が可能となり、複雑なジェネレータの連鎖を簡潔に書けるようになる。

  • サブジェネレータのクローズや戻り値の処理、例外の伝播などの高度な機能を活用することで、より柔軟なジェネレータの制御が可能になる。

  • これらの技術を組み合わせることで、移動平均の計算やファイル操作などの実践的なタスクを効率的に実装できる非同期プログラミングパターンを構築できる。

ジェネレータはPythonの基本的な機能であり、シーケンスやデータストリームを効率的に処理することを可能にする。多くの開発者が基本的なジェネレータの使用に慣れている一方で、コルーチン、委譲、複雑な例外処理などの強力なパターンを実現する高度なテクニックまで掘り下げる人は少ない。

この投稿では、以下に焦点を当てた高度なジェネレータテクニックを探求する:

  • サブジェネレータへの委譲に`yield from`を使用する

  • サブジェネレータのクローズと戻り値の処理方法を理解する

  • ジェネレータ委譲チェーンを通じた例外の伝播

これらの概念は、Pythonで清潔で効率的かつ保守可能な非同期コードを書く上で重要である。


ジェネレータ委譲と`yield from`の概要

Pythonでは、`yield from`文を使用して、ジェネレータの操作の一部を別のジェネレータ(「サブジェネレータ」)に委譲する。これは特に以下の場合に有用である:

  • 明示的なループを排除することでジェネレータコードを簡略化する

  • 呼び出し元とサブジェネレータの間に双方向通信チャネルを確立する

基本例:

def subgenerator():
    yield from range(3)

def delegator():
    yield from subgenerator()

for value in delegator():
    print(value)

出力:

0
1
2

この例では、`delegator()`関数は`yield from`を使用して、`subgenerator()`からのすべての値を直接呼び出し元にイールドする。


サブジェネレータのクローズと戻り値の処理

`yield from`のクローズ時の動作

`yield from`を使用する場合、委譲者は、サブジェネレータが使い果たされる(つまり、`StopIteration`を発生させる)まで`yield from`式で「一時停止」される。この間:

  • 呼び出し元は、委譲者を介してサブジェネレータに直接値や例外を送ることができる

  • 委譲者は、サブジェネレータがクローズされるまで非アクティブのままである

サブジェネレータのクローズ例:

def subgen():
    try:
        while True:
            received = yield
            print(f"サブジェネレータが受信: {received}")
    finally:
        print("サブジェネレータ: クローズ中...")

def delegator():
    yield from subgen()
    print("委譲者: サブジェネレータがクローズされた")

# 使用例
d = delegator()
next(d)  # ジェネレータを準備

d.send("Hello")
# 出力: サブジェネレータが受信: Hello

d.close()
# 出力:
# サブジェネレータ: クローズ中...
# 委譲者: サブジェネレータがクローズされた

`d.close()`を呼び出すと:

  1. サブジェネレータ内で`GeneratorExit`例外が発生する

  2. サブジェネレータは`finally`ブロックでクリーンアップを処理して終了する

  3. 制御が委譲者に戻り、`yield from`文の後の実行が継続される

サブジェネレータからの戻り値の取得

`yield from`文は単なる値の導管以上のものである。これはサブジェネレータの戻り値に評価される式でもある。

戻り値を含む例:

def subgen():
    yield 1
    yield 2
    return "サブジェネレータの結果"

def delegator():
    result = yield from subgen()
    print(f"委譲者が受信: {result}")

# 使用例
for value in delegator():
    print(value)

出力:

1
2
委譲者が受信: サブジェネレータの結果

ここでは、サブジェネレータが戻るとき、委譲者の`yield from`式はサブジェネレータの戻り値に評価され、それを`result`で捕捉する。


ジェネレータへの例外の投げ入れ

ジェネレータは`.throw()`メソッドを使用して投げ入れられた例外を処理できる。これは特にジェネレータ委譲と組み合わせると非常に強力である。

委譲されたジェネレータでの例外伝播

例外が委譲者に投げ入れられた場合、委譲者がそれを処理しなければ、サブジェネレータに伝播される。

例:

def subgen():
    try:
        while True:
            received = yield
            print(f"サブジェネレータが受信: {received}")
    except ValueError:
        print("サブジェネレータがValueErrorを処理")
        yield "ValueErrorから回復"

def delegator():
    yield from subgen()

# 使用例
d = delegator()
next(d)  # ジェネレータを準備

d.send("Hello")
# 出力: サブジェネレータが受信: Hello

result = d.throw(ValueError)
# 出力: サブジェネレータがValueErrorを処理

print(f"委譲者が受信: {result}")
# 出力: 委譲者が受信: ValueErrorから回復

サブジェネレータと委譲者での例外処理

サブジェネレータが例外を処理しない場合、それは委譲者に浮上し、委譲者はそれを処理するか、さらに伝播させるかを選択できる。

委譲者による例外処理:

def subgen():
    while True:
        received = yield
        print(f"サブジェネレータが受信: {received}")

def delegator():
    try:
        yield from subgen()
    except ValueError:
        print("委譲者がValueErrorを処理")

# 使用例
d = delegator()
next(d)  # ジェネレータを準備

d.send("Hello")
# 出力: サブジェネレータが受信: Hello

d.throw(ValueError)
# 出力: 委譲者がValueErrorを処理

この場合、サブジェネレータが`ValueError`を処理しないため、それは委譲者に伝播し、委譲者がそれを処理する。


実践例:移動平均のコルーチン

これらの概念を組み合わせた実践例を作成しよう—移動平均を計算し、ファイルへのデータ書き込みなどの特定のアクションを実行するために例外を処理できるコルーチンである。

カスタム例外:

class WriteAverage(Exception):
    pass

コルーチンの実装:

def averager(outfile):
    total = 0
    count = 0
    average = None
    with open(outfile, 'w') as f:
        f.write('count,average\n')
        while True:
            try:
                received = yield average
                total += received
                count += 1
                average = total / count
            except WriteAverage:
                if average is not None:
                    print(f"平均値 {average} をファイルに保存")
                    f.write(f"{count},{average}\n")

使用例:

avg = averager('averages.csv')
next(avg)  # コルーチンを準備

avg.send(10)
avg.send(20)
print(avg.send(30))  # 出力: 20.0

avg.throw(WriteAverage)
# 出力: 平均値 20.0 をファイルに保存

avg.send(40)
print(avg.send(50))  # 出力: 30.0

avg.throw(WriteAverage)
# 出力: 平均値 30.0 をファイルに保存

avg.close()

結果ファイル(`averages.csv`):

count,average
3,20.0
5,30.0

この例では:

  • `averager`コルーチンが移動平均を計算する

  • `WriteAverage`例外がコルーチンに投げ入れられると、現在の平均値をファイルに書き込む

  • コルーチンは例外を内部で処理し、値の処理を継続する


結論

Pythonの高度なジェネレータテクニック(`yield from`の使用、クローズの処理、例外の伝播など)は、複雑な非同期パターンとコルーチンを構築するための強力なツールを提供する。これらの概念を理解することで、以下が可能になる:

  • 委譲を通じたジェネレータコードの簡略化

  • サブジェネレータのライフサイクル管理と戻り値の取得

  • ジェネレータチェーンにわたる堅牢な例外処理の実装

これらのテクニックを習得することで、特に非同期操作、データ処理パイプライン、協調的マルチタスクを含むアプリケーションにおいて、より効率的で保守可能なコードを書くことができる。


重要なポイント:

  • `yield from`による委譲: サブジェネレータへの委譲とコードの簡略化に`yield from`を使用する

  • ジェネレータのクローズ: サブジェネレータがクローズされると、委譲者は`yield from`文の後の実行を再開できる

  • 戻り値の取得: `yield from`はサブジェネレータの戻り値に評価される式である

  • 例外の伝播: 委譲者に投げ入れられた例外はサブジェネレータに渡される。処理されない場合、委譲者によって捕捉されるか、さらに呼び出し元に伝播される

  • 実践的な応用: これらのテクニックを組み合わせることで、ファイルへの書き込みなどの特定のアクションを実行するための一時停止、再開、または例外を介した制御フローを処理できるコルーチンなどの高度なパターンが可能になる


お読みいただきありがとうございます!この記事が役に立った場合は、他の方々にも共有していただけると幸いです。コメントや質問がございましたら、下記にお寄せください。


「超本当にドラゴン」へ

いいなと思ったら応援しよう!