关键技术一:yield, next, send, throw

要想实现异步的效果,我们需要在 必要 的时候,来控制cpu执行我们想让它执行的代码,比如,在发生io等待时,我们需要让cpu去执行其他代码,等到io完成时,再继续执行刚才的代码,这个的实现(控制代码执行顺序)就要用到 yieldnextsend等这一系列的关键字。

yield

如果在一个方法中使用了yield关键字,那么我们称这个方法为生成器,其中 yield 的作用,就是在执行到yield语句时,将其后面紧跟的值返回给调用放,并让出cpu,看一下下面的代码

def a():                                    #1
    for u in range(3):                      #2
        yield u                             #3
        print(f" - - {u} - -")              #4
                                            #5
    return "end"                            #6

a_g = a()                                   #7     创建生成器
a_g_1 = next(a_g)                           #8     激活生成器,代码执行到 #3,碰到yield 后返回 (这里就是我们自己来控制cpu的执行顺序),并将yield后的值赋给 a_g_1
print(a_g_1)                                #9

a_g_2 = next(a_g)                           #10    调用后,cpu会返回到之前碰到yield的地方,也就是 #3, 然后继续往下执行,直到再次碰到 yield, 再次跳出正在执行的函数,并将yield后的值返回给a_g_2, 然后继续往下执行
print(a_g_2)                                #11

# 输出
# 0
# - - 0 - -
# 1

# Process finished with exit code 0

可以将上面到 #1 到 #11 的代码拷到本地,debug一下,会发现,代码的执行顺序是, #7 -> #8 -> #2 -> #3 -> #9 -> #10 -> #4 -> #2 -> #3 ...

就在这个代码片段中,我们实现了,自己调度cpu,让它按照我们需要的顺序去执行代码,而这一切主要是通过yield 实现的,yield在这里做的:

一个是在需要的时候,让cpu按我们指定顺序执行代码

另一个是,保存正在执行的上下文环境,保证在需要的时候,再回到跳出来的地方

next

生成器的调用不同于正常的函数,上述代码中a()并不会执行a这个生成器,而是需要特殊的方法,可用的方法如下:

  • next方法,就像上面看到的那样,next 方法会触发生成器,并一直往下执行,知道碰到yield或执行完毕,抛出 StopIteration 异常 (如果此生成器有return的值,此异常的value值就是return的值)

  • for 方法,其实for循环的实现也是通过不断的调用一个迭代器的__next__方法,直到碰到StopIteration异常,捕获处理,所以跟next一样

  • send 方法,send挺关键的,下面单独说

  • throw 方法,a_g.throw() 接收一个异常,直接抛出,没怎么用过

send

我们来说说send, 为什么说 send 关键呢?
可以想像一下,假如我们想在继续执行yield 函数的代码时,给它传递一个实时的参数,怎么办?

使用send,像这样:

def test_a():
    """
    """
    res  = yield 5             # 这里res会接收 send的值
    print(res)

a_g_1 = test_a()
b = a_g_1.send(None)           # 这里注意,使用send 激活生成器时,第一次必须使用None,否则会报 **TypeError: can't send non-None value to a just-started generator**
                                         # b = 5

以上,我们就可以在需要的时候,在单线程内,控制代码的执行顺序,并能在进行调用方和被调用方之间双向通信。

下次我们来实现一个类似asyncio的效果。