generator.throw()有什么好处?

2024-05-16 04:32:02 发布

您现在位置:Python中文网/ 问答频道 /正文

PEP 342 (Coroutines via Enhanced Generators)向生成器对象添加了一个throw()方法,该方法允许调用方在生成器内部引发一个异常(好像它是由yield表达式引发的)。

我想知道这个特性的用例是什么。


Tags: 对象方法表达式特性用例viapepenhanced
3条回答

假设我使用一个生成器来处理向数据库中添加信息的操作;我使用这个生成器来存储网络接收到的信息,通过使用生成器,我可以在实际接收到数据时高效地执行此操作,并执行其他操作。

所以,我的生成器首先打开一个数据库连接,每次发送数据时,它都会添加一行:

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)

一切都很好;每次我的数据都会插入一行。

但如果我的数据库是事务性的呢?当将数据提交到数据库时,如何向该生成器发送信号?何时中止交易?而且,它持有一个到数据库的开放连接,也许我有时希望它关闭该连接以回收资源。

这就是.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异常与^{组合来关闭正在运行的生成器。

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

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

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

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

我认为throw()方法之所以有用有很多原因。

  1. 对称性:没有强烈的理由认为异常情况应该只在调用者中处理,而不是在生成器函数中处理。(假设从数据库读取值的生成器返回一个错误值,并且假设只有调用方知道该值是错误的。使用throw()方法,调用者可以向生成器发出信号,指出存在必须纠正的异常情况。)如果生成器可以引发异常,而被调用者截获,则也可以进行相反的操作。

  2. 灵活性:生成器函数可能有多个yield语句,调用方可能不知道生成器的内部状态。通过抛出异常,可以将生成器重置为已知状态,或者实现更复杂的流控制,这对于单独使用next()send()close()是非常麻烦的。

询问用例可能会产生误导:对于每个用例,您可以生成一个反例,而无需使用throw()方法,并且讨论将一直持续下去。。。

相关问题 更多 >