63

PEP 342(Coroutines via Enhanced Generators)向生成器对象添加了一个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()来关闭正在运行的生成器。

所有这些都是协程如何工作的重要基础。协程本质上是生成器,加上一些额外的语法可以使编写协程更容易和更清晰。但在引擎盖下,它们仍然建立在相同的屈服和发送之上。当您并行运行多个协程时,如果其中一个协程失败,您需要一种方法来干净地退出这些协程,仅举一个例子。

于 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

一个用例是在发生异常时在堆栈跟踪中包含有关生成器内部状态的信息——否则调用者将看不到这些信息。

例如,假设我们有一个如下所示的生成器,其中我们想要的内部状态是生成器的当前索引号:

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 回答
4

这个“答案”更像是一个琐事。

我们可以(ab)使用生成器throw()在 lambda 中引发异常,否则它不支持该raise语句。

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

引自https://stackoverflow.com/a/8294654/728675

于 2019-10-19T18:39:00.487 回答