web-dev-qa-db-ja.com

generator.throw()は何に適していますか?

PEP 342(拡張ジェネレーターを介したコルーチン) ジェネレーターオブジェクトにthrow()メソッドを追加しました。これにより、呼び出し元は例外insideジェネレーター(yield式によってスローされたかのように)。

この機能の使用例は何でしょうか。

54
NikiC

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

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

_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つが失敗した場合に、それらのコルーチンをクリーンに終了する方法が必要です。

56
Martijn Pieters

私の意見では、throw()メソッドは多くの理由で役立ちます。

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

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

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

10
Stefano M

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")
3
cjerdonek

この「答え」は雑学クイズのようなものです。

ジェネレーターのthrow()を使用して、ラムダ内でExceptionを発生させることができます。これは、他の方法ではraiseステートメントをサポートしていません。

foo = lambda: (_ for _ in ()).throw(Exception('foobar'))

https://stackoverflow.com/a/8294654/728675 から引用

1
RayLuo