63

PEP 342 (強化されたジェネレーターによるコルーチン)は、ジェネレーター オブジェクトにメソッドを追加しましたthrow()。これにより、呼び出し元はジェネレーターで例外を発生させることができます(あたかもyield式によってスローされたかのように)。

この機能のユースケースは何だろうと思っています。

4

4 に答える 4

72

データベースへの情報の追加を処理するためにジェネレーターを使用するとします。これを使用してネットワークから受信した情報を保存します。ジェネレーターを使用することで、実際にデータを受信するたびに効率的にこれを実行し、それ以外のことを行うことができます。

したがって、私のジェネレーターは最初にデータベース接続を開き、何かを送信するたびに行を追加します。

def add_to_database(connection_string):
    db = mydatabaselibrary.connect(connection_string)
    cursor = db.cursor()
    while True:
        row = yield
        cursor.execute('INSERT INTO mytable VALUES(?, ?, ?)', row)

それはすべて問題ありません。データを取得するたびに.send()、行が挿入されます。

しかし、データベースがトランザクション対応の場合はどうなるでしょうか? データをデータベースにコミットするタイミングをこのジェネレーターに通知するにはどうすればよいですか? そして、いつトランザクションを中止しますか? さらに、データベースへの開いた接続を保持しているため、リソースを再利用するためにその接続を閉じたい場合があります。

これが.throw()メソッドの出番です。そのメソッドで.throw()例外を発生させて、特定の状況を知らせることができます。

def add_to_database(connection_string):
    db = mydatabaselibrary.connect(connection_string)
    cursor = db.cursor()
    try:
        while True:
            try:
                row = yield
                cursor.execute('INSERT INTO mytable VALUES(?, ?, ?)', row)
            except CommitException:
                cursor.execute('COMMIT')
            except AbortException:
                cursor.execute('ABORT')
    finally:
        cursor.execute('ABORT')
        db.close()

.close()ジェネレーターのメソッドは、本質的に同じことを行います。GeneratorExitと組み合わせた例外を使用して.throw()、実行中のジェネレーターを閉じます。

これらすべてが、コルーチンがどのように機能するかの重要な基盤です。コルーチンは本質的にジェネレーターであり、コルーチンをより簡単かつ明確に記述するためのいくつかの追加の構文があります。しかし内部では、それらは依然として同じ譲歩と送信に基づいて構築されています。また、複数のコルーチンを並行して実行している場合、例を挙げると、それらの 1 つが失敗した場合にそれらのコルーチンをきれいに終了する方法が必要です。

于 2012-07-14T20:13:07.103 に答える
14

私の意見では、このthrow()方法は多くの理由で役立ちます。

  1. 対称性: 例外的な条件を呼び出し元でのみ処理し、ジェネレーター関数では処理しないという強い理由はありません。(データベースから値を読み取るジェネレーターが不適切な値を返し、その値が不適切であることを呼び出し元だけが知っているとします。このthrow()メソッドを使用すると、呼び出し元は、修正が必要な異常な状況が発生したことをジェネレーターに通知できます。 ) ジェネレーターが呼び出し元によってインターセプトされた例外を発生させることができる場合、その逆も可能である必要があります。

  2. 柔軟性: ジェネレーター関数には複数のyieldステートメントが含まれる場合があり、呼び出し元はジェネレーターの内部状態を認識しない場合があります。例外をスローすることで、ジェネレーターを既知の状態にリセットnext()したり、 , send(),close()だけでは面倒なより洗練されたフロー制御を実装したりできます。

内部状態をリセットする例:

def gen():
    try:
        yield 10
        print("State1")
        yield 20
        print("State2")
        yield 30
        print("State3")
    
   except:
        #Reset back to State1!
        yield gen()

g = gen()
print(next(g))
print(next(g))
g = g.throw(ValueError) #state of g has been reset
print(next(g))

>>10
>>State1
>>20
>>10

ユースケースを尋ねることは誤解を招く可能性があります.すべてのユースケースに対して、メソッドを必要とせずに反例を作成できthrow()、議論は永遠に続きます.

于 2012-07-17T18:47:27.947 に答える
7

ユース ケースの 1 つは、例外が発生したときにジェネレーターの内部状態に関する情報をスタック トレースに含めることです。この情報は、呼び出し元には表示されません。

たとえば、必要な内部状態がジェネレーターの現在のインデックス番号である、次のようなジェネレーターがあるとします。

def gen_items():
    for i, item in enumerate(["", "foo", "", "foo", "bad"]):
        if not item:
            continue
        try:
            yield item
        except Exception:
            raise Exception("error during index: %d" % i)

次のコードは、追加の例外処理をトリガーするには不十分です。

# Stack trace includes only: "ValueError: bad value"
for item in gen_items():
    if item == "bad":
        raise ValueError("bad value")

ただし、次のコードは内部状態を提供します。

# Stack trace also includes: "Exception: error during index: 4"
gen = item_generator()
for item in gen:
    if item == "bad":
        gen.throw(ValueError, "bad value")
于 2014-12-06T19:45:49.697 に答える