美文网首页
Python的 yield 表达式

Python的 yield 表达式

作者: 东方胖 | 来源:发表于2022-04-14 13:36 被阅读0次

程序运行的入口点

一般我们初次接触编程,写的顺序执行模型的程序的入口点只有 main 函数。
从 main 函数开始,按照顺序执行每一个函数。
如果没有任何异步模型的话,好比一个糖葫芦串一样,从头到尾吃到最后

执行流

把视野缩小到一个函数体——每一个进程在操作系统里,可以看成一个运行实体,本质上,只要愿意,进程的行为实际上都可以在一个函数体内实现——大体上如此,所以我们讨论入口点的时候可以把视野先放到一个函数体内。

函数的执行,需要告诉执行器入口点的位置,一般是一个函数名,在 C 世界,函数名对应了函数指针,内存中有一个独一无二的地址。
除此以外,类似 goto 这种跳转,能够为程序“开辟”一个跳转到异端执行逻辑的入口

程序的入口点为何如此重要

把函数看成一个积木,它只有首位两端可以连接,那么我们构建一个执行流,毫无疑问只能像糖葫芦串那样一个一个首位相接,它会变成一个长长的串。
我们假定这个串有严格的先后顺序,很容易将它对应到一个时间流里,这就是我们最初接触到的串行编程模型。

因为 CPU 的享用是独占式的,一旦执行流在糖葫芦串的前面,后面的执行流只能先等待。有一些任务如文件读取,TCP 数据读写,程序会“陷入”内核,程序会进入一种暂停等待的状态,空余的 CPU 继续闲着不是一个好的模型,于是人们很早的时候就想到了并发。

Go 语言发明者之一 Rob Pike 的教育: 并发不是并行(Concurrency is not Parallelism)

下面两个执行流分别代表串行和并发模型


串行流 并发流

可以看到并发模型能将三个任务的整体前置时间线前移。

问题是

  • 当我们开启计算任务组task1 和 task2 之后,怎么在该任务未完成的状态下又“钻到”另一个任务的入口去?
  • 当我们完成下载任务后,怎么回到也已经开启的任务 tcp流中去。

这就需要一种不限于从函数调用的方式开启入口点的方式才能支持。

方式有数种

  • 一个是进程。多进程本就是操作系统支持并发流的方式。
  • 另一个是线程。一个线程对应一个新的入口点,它和主线程没有主次之分,如果你不作任何同步,几个线程开启,各自如野马狂奔。
  • 还有一个就是协程。也是本文讨论 yield 表达式牵连到的主题

yield 和协程

协程和进程最重要的区别可能是,进程的调度是被动的,默认情形下它很机械的向前执行,它的调度完全依赖外部的机制,线程也是类似的。而协程是主动“停下来”,让出CPU的控制权。

当然协程停下来不是让自己“死去”,而是钻进了一个类似太空冷藏室的地方,它的肌肉,细胞,记忆等等内部组织都决定暂停的时候封存起来——即内部变量,栈帧等等状态会原封不动存储——等待下一次激活。
激活之后,协程会继承之前的状态继续运行下去,知道下一次暂停或结束。

设想孙悟空定住摘蟠桃的七仙女,大圣吃完桃子之后,七仙女解封,继续摘桃子,七仙女的行为类似一个协程的暂停,虽然她不是自己主动暂停的,协程通常是自己主动停下来——即停下来的逻辑放在执行流里。

在Python 能支持这个控制的表达式就是 yield 以及 yield from (3.4.2)

yield 机制

例子——求移动平均

def average():
    total = 0
    n = 0
    average = None
    while True:
        number = yield average
        total += number
        n += 1
        average = total // n
$ gen = average() 
$ next(gen)
$ gen.send(100)

yield 一旦出现,把函数变成一个生成器,调用 gen = average() 不会运行 average内部的逻辑,只是起到一个声明的作用
打印 type(gen) 会输出 <generator object average at 0x109844820>
next 内建函数可以激活一个生成器
运行到 yield 后面的表达式处为止,生成器挂起,让出 CPU 控制权。

yield 除了挂起的作用,还有一个作用是它留了一个"入口点" 让别的调用方可以通过某种手段找到并重新激活它,这个方式就是 send函数。

gen.send(value) value 会从入口点送到生成器挂起的地方,并赋值给 变量 number ,并重新激活生成器。

整个过程,我们做一个类比。
有两个人在守门值班,A 困了以后,打了一个电话给 B,说该你了,并且给 B 一个值班日志,B拿到值班日志继续值班,A 睡觉去了,直到下一次轮班,A再把 B 叫醒,递给他一份更新过得值班日志,A继续值班。

<value> = yield <expr> 右边可以跟一个表达式,表达式的计算完之后会带出去给调用方,左边是一个变量,它可以接收调用方传入的一个值

yield 使得函数的入口点可以突破常规,从而给了一种实现协程机制的可能性。

  • yield 可以单独出现,既不接受调用方的任何东西,也不吐出任何东西。单独的 yield 出现时,这样说也不是特别地确切,因为实际上,它还是向外传递了 None ,如果认为 None 不是个东西的话,这么说也是没什么问题的。
  • 当然 yield 也可以只含有右边的表达式,这意味着外部给的任何东西都会被忽略掉。这时候调用方传给 send() 什么参数都不重要,他只是起到一个重新激活的作用。

可见,yield 最重要的作用是流程控制

yield 和生成器

一般来说,生成器几乎就是一个协程,但是还是有区别,在 Python 2.5 版本之前, yield 的作用还不曾延及到左边那一半,只有一个右边放一个表达式这种用法,截止到 Python2.4 yield 是完全和生成器绑定在一起。
Python2.5 引入了左边的表示,并新增了诸如 send, next 以及 throw close 等 API,可以看做是Python协程最初的雏形, 这些东西在 PEP342 描述

相关文章

  • python中的yield

    第一次看到yield是在python学习手册上,在python表达式操作符这一节:操作符 :yield x 描述...

  • Python协程一从generator和yield表达式说起

    Python协程系列(一)——从generator和yield表达式说起 python进阶教程 机器学习 进入正文...

  • 15. Python之yield表达式和生成式

    1 yield表达式的应用 1.1 利用yield返回值给函数体的变量传值 1.2 yield表达式格式生成器 通...

  • python yield和yield from用法总结

    python yield和yield from用法总结 yield 作用: 注: generator的next()...

  • Python的 yield 表达式

    程序运行的入口点 一般我们初次接触编程,写的顺序执行模型的程序的入口点只有 main 函数。从 main 函数开始...

  • Python yield关键字

    Python中yield关键字解释 这篇文章关于python的yield关键字。并且文章中会解释什么是yield,...

  • python: yield

    python: yield

  • 理解geneartor

    value 值指yield后面 执行后 表达式的值,例如‘hello’ m 是执行后返回的值 即yield+表达式...

  • 理解Python的协程(Coroutine)

    生成器(Generator)yield表达式的使用生产者和消费者模型yield from表达式 协程(Corout...

  • Generator

    介绍 (1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的...

网友评论

      本文标题:Python的 yield 表达式

      本文链接:https://www.haomeiwen.com/subject/wfobcrtx.html