南昌h5建站,长沙市公司网站设计,10大工程必备软件,ui设计网课第 35 条 不要通过 throw 变换生成器状态
除 yield from 表达式(参见 第 33 条) 与 send 方法#xff08;参见 第 34 条#xff09;外#xff0c;生成器还有一个高级功能#xff0c;就是可以把调用者通过 throw 方法传过来的 Exception 实例重新抛出。这个 throw 方法用…第 35 条 不要通过 throw 变换生成器状态
除 yield from 表达式(参见 第 33 条) 与 send 方法参见 第 34 条外生成器还有一个高级功能就是可以把调用者通过 throw 方法传过来的 Exception 实例重新抛出。这个 throw 方法用起来很简单如果调用这个方法那么生成器下次推进时就不会像平常那样直接走到下一条 yield 表达式那里而是通过 throw 方法传入的异常重新抛出。下面用代码演示这种效果。
class MyError(Exception):passdef my_generator():yield 1yield 2yield 3it my_generator()
print(next(it))
print(next(it))
print(it.throw(MyError(test error)))
__main__.MyError: test error
1
2 生成器函数可以用标准的 try/expect 复合语句把 yield 表达式包裹起来如果函数执行到了这条表达式这里而这次即将继续执行时又发现外界通过 throw 方法给自己注入了异常那么这个异常就会被 try 结构捕获下来如果捕获之后不继续抛出异常那么生成器函数就会推进到下一条 yield 表达式(更多异常处理参见 第 65 条)。
class MyError(Exception):passdef my_generator():yield 1try:yield 2except MyError:print(Get Error)else:yield 3yield 4it my_generator()
print(next(it)) # Yield 1
print(next(it)) # Yield 2
print(it.throw(MyError(test error)))
1
2
Get Error
4 这项机制会在生成器与调用者形成双向通道(另一种双向通信通道, 参见第34条)这在某些情况下是有用的。例如要编写一个可以重置的计时器程序。笔者定义下面的 Reset 异常与 Timer 生成器方法让调用者可以在 timer 给出的迭代器上通过 throw 方法注入 Reset 异常令计时器重置。
def Reset(exception):passdef timer(period):current periodwhile current:current - 1try:yield currentexcept Reset:current period 按照这种写法如果 timer 正准备从 yield 表达式往下递推时发现有人注入了 Reset 异常那么它就会把这个异常捕获下来并进入 except 分支在这里它会把表示倒计时的 current 变量调整成最初的 period 值。
这个计时器可以与外界某个按秒查询的输入机制对接起来。笔者定义一个函数以驱动 timer 生成器所给出的那个 it 迭代器并根据外界的情况做处理如果外界要求重置那就通过 it 迭代器的 throw 方法给计时器注入 Reset 变量如果外界没有这样要求那就调用 annouce 函数打印所给的倒计时值。
def check_for_reset():# Poll for external event...def announce(remaining):print(f{remaining} ticks remaining)def run():it timer(4)while True:try:if check_for_reset():current it.throw(Reset())else:current next(it)except StopIteration:breakelse:announce(current)run()
3 ticks remaining
2 ticks remaining
1 ticks remaining
3 ticks remaining
2 ticks remaining
1 ticks remaining
3 ticks remaining
2 ticks remaining
1 ticks remaining
0 ticks remaining 这样写用了很多嵌套结构我们要判断 check_for_reset() 函数的返回值以确定是应该通过 it.throw 注入 Reset 异常还是应该通过 next 推进迭代器。如果要是推进迭代器那么还得捕获 StopIteration 异常若是捕获到了这种异常那说明迭代器已经走到终点则要执行 break 跳出 while 循环; 如果没有捕获到则应该采用 annouce 函数打印倒计时。这会让代码很乱。
有个简单的办法能够改写这段代码那就是利用可迭代的容器对象(参见 第 31 条)定义一个有状态的必包参见 第 38 条。下面的代码就写了这样一个 Timer 类并通过它重新实现刚才的 timer 生成器。
class Timer:def __init__(self, period):self.current periodself.period perioddef reset(self):self.current self.perioddef __iter__(self):while self.current:self.current - 1yield self.current 现在run 函数就好写多了因为它只需要用 for 循环 迭代这个 timer 即可。
def run():timer Timer(4)for current in timer:if check_for_reset():timer.reset()announce(current)
run()
3 ticks remaining
2 ticks remaining
1 ticks remaining
3 ticks remaining
2 ticks remaining
1 ticks remaining
3 ticks remaining
2 ticks remaining
1 ticks remaining
0 ticks remaining 这样写所输出的结果与前面一样但是这种实现方法理解起来更容易。凡是想用生成器与异常来实现的功能通常都可以改用异步机制去做参见 第 60 条。如果确实遇到了这里讲到的这种需求那么更应该通过可迭代的类实现生成器而不要用 throw 方法注入异常。