30.4.9. 不要通过throw变换生成器的状态

除yield from表达式(参见第33条)与send方法外,生成器还有一项高级功能,就是可以把调用者通过throw方法传来的Exception实例重新抛出。这个throw方法用起来很简单:如果调用了这个方法,那么生成器下次推进时,就不会像平常那样,直接走到下一条yield表达式那里,而是会把通过throw方法传入的异常重新抛出。

下面用代码演示这种效果。

class MyError(Exception):
    pass


def my_generator():
    yield 1
    yield 2
    yield 3


it = my_generator()
print(next(it))
print(next(it))
print(it.throw(MyError("test error")))

生成器函数可以用标准的try/except复合语句把yield表达式包裹起来,如果函数上次执行到了这条表达式这里,而这次即将继续执行时,又发现外界通过throw方法给自己注入了异常,那么这个异常就会被try结构捕获下来,如果捕获之后不继续抛异常,那么生成器函数会推进到下一条yield表达式。

#!/usr/bin/env python
# -*- coding:utf8 -*-
class MyError(Exception):
    pass


def my_generator():
    yield 1

    try:
        yield 2
    except MyError:
        print("Got MyError!")
    else:
        yield 3

    yield 4


it = my_generator()
print(next(it))
print(next(it))
print(it.throw(MyError("test error")))

要点:

  • throw方法可以把异常发送到生成器刚执行过的那条yield表达式那里,让这个异常在生成器下次推进时重新抛出。通过throw方法注入异常,会让代码变得难懂,因为需要用多层嵌套的模板结构来抛出并捕获这种异常。

  • 如果确实遇到了这样的特殊情况,那么应该通过类的__iter__方法实现生成器,并且专门提供一个方法,让调用者通过这个方法来触发这种特殊的状态变换逻辑。