PEP 342 (Coroutines via Enhanced Generators)向生成器对象添加了throw()
方法,允许调用者在生成器内引发异常(好像它是由{{1}引发的)表达)。
我想知道这个功能的用例是什么。
答案 0 :(得分:56)
假设我使用生成器处理向数据库添加信息;我使用它来存储网络接收的信息,并且通过使用生成器,我可以在实际接收数据时有效地执行此操作,否则执行其他操作。
所以,我的生成器首先打开一个数据库连接,每次你发送它时,它都会添加一行:
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()
结合来关闭正在运行的生成器。
所有这些都是 coroutines 如何运作的重要基础;协同程序本质上是生成器,以及一些额外的语法,使协同写入更容易和更清晰。但在引擎盖下,它们仍然建立在相同的屈服和发送上。当你并行运行多个协同程序时,你需要一种方法来干净地退出这些协同程序,如果其中一个失败了,只是举一个例子。
答案 1 :(得分:10)
在我看来,throw()
方法有很多原因。
对称性:没有强有力的理由只应在调用者中处理异常条件,而不是在生成器函数中处理异常条件。 (假设数据库中的生成器读取值返回错误值,并假设只有调用者知道该值是坏的。使用throw()
方法,调用者可以向生成器发信号通知存在异常情况必须纠正。)如果发生器可以引发一个由调用者拦截的异常,那么反过来也是可能的。
灵活性:生成器函数可能有多个yield
语句,调用者可能不知道生成器的内部状态。通过抛出异常,可以将生成器重置到已知状态,或实现更复杂的流控制,这对next()
,send()
,{{{{{}}来说会更加麻烦。 1}}单独。
询问用例可能会产生误导:对于每个用例,您都可以在不需要close()
方法的情况下生成一个反例,并且讨论会永远继续......
答案 2 :(得分:3)
一个用例是在发生异常时包含有关堆栈跟踪中生成器内部状态的信息 - 调用者无法看到的信息。
例如,假设我们有一个像下面这样的生成器,我们想要的内部状态是生成器的当前索引号:
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 :(得分:1)
此“答案”更像是琐事。
我们可以(ab)使用生成器的throw()
在lambda内引发Exception,否则该lambda不支持raise
语句。
foo = lambda: (_ for _ in ()).throw(Exception('foobar'))