原文链接:https://www.cnblogs.com/vipchenwei/p/7049404.html

yield与send实现协程操作

之前我们说过,在函数内部含有yield语句即称为生成器。

下面,我们来看看在函数内部含有yield语句达到的效果。首先,我们来看看以下代码:

def foo():while True:x = yieldprint("value:",x)g = foo() # g是一个生成器
next(g) # 程序运行到yield就停住了,等待下一个next
g.send(1)  # 我们给yield发送值1,然后这个值被赋值给了x,并且打印出来,然后继续下一次循环停在yield处
g.send(2)  # 同上
next(g)  # 没有给x赋值,执行print语句,打印出None,继续循环停在yield处

我们都知道,程序一旦执行到yield就会停在该处,并且将其返回值进行返回。上面的例子中,我们并没有设置返回值,所有默认程序返回的是None。我们通过打印语句来查看一下第一次next的返回值:

print(next(g))####输出结果#####
None

正如我们所说的,程序返回None。接着程序往下执行,但是并没有看到next()方法。为什么还会继续执行yield语句后面的代码呢?这是因为,send()方法具有两种功能:第一,传值,send()方法,将其携带的值传递给yield,注意,是传递给yield,而不是x,然后再将其赋值给x;第二,send()方法具有和next()方法一样的功能,也就是说,传值完毕后,会接着上次执行的结果继续执行,知道遇到yield停止。这也就为什么在调用g.send()方法后,还会打印出x的数值。有了上面的分析,我们可以很快知道,执行了send(1)后,函数被停止在了yield处,等待下一个next()的到来。程序往下执行,有遇到了send(2),其执行流程与send(1)完全一样。

  有了上述的分析,我们可以总结出send()的两个功能:1.传值;2.next()。

  既然send()方法有和next一样的作用,那么我们可不可以这样做:

def foo():while True:x = yieldprint("value:",x)g = foo()
g.send(1) #执行给yield传值,这样行不行呢?

 很明显,是不行的。

TypeError: can't send non-None value to a just-started generator

错误提示:不能传递一个非空值给一个未启动的生成器。

  也就是说,在一个生成器函数未启动之前,是不能传递数值进去。必须先传递一个None进去或者调用一次next(g)方法,才能进行传值操作。至于为什么要先传递一个None进去,可以看一下官方说法。

Because generator-iterators begin execution at the top of thegenerator's function body, there is no yield expression to receivea value when the generator has just been created.  Therefore,calling send() with a non-None argument is prohibited when thegenerator iterator has just started, and a TypeError is raised ifthis occurs (presumably due to a logic error of some kind).  Thus,before you can communicate with a coroutine you must first callnext() or send(None) to advance its execution to the first yieldexpression.

问题就来,既然在给yield传值过程中,会调用next()方法,那么是不是在调用一次函数的时候,是不是每次都要给它传递一个空值?有没有什么简便方法来解决這个问题呢?答案,装饰器!!看下面代码:

def deco(func):  # 装饰器:用来开启协程def wrapper():res = func()next(res)return res  # 返回一个已经执行了next()方法的函数对象return wrapper
@deco
def foo():pass

上面我yield是没有返回值的,下面我们看看有返回值的生成器函数。

def deco(func):def wrapper():res = func()next(res)return resreturn wrapper
@deco
def foo():food_list = []while True:food = yield food_list  #返回添加food的列表food_list.append(food)print("elements in foodlist are:",food)
g = foo()
print(g.send('苹果'))
print(g.send('香蕉'))
print(g.send('菠萝'))
###########输出结果为######
elements in foodlist are: 苹果
['苹果']
elements in foodlist are: 香蕉
['苹果', '香蕉']
elements in foodlist are: 菠萝
['苹果', '香蕉', '菠萝']

分析:首先,我们要清楚,在函数执行之前,已经执行了一次next()(装饰器的功能),程序停止yield。接着程序往下执行,遇到g.send(),然后将其值传递给food,然后再将获得的food添加到列表food_list中。打印出food,再次循环程序停在yield。程序继续执行,又遇到g.send(),其过程与上面是一模一样的。看看以下的程序执行流程,你可能会更清楚。

  这里我们要明确一点,yield的返回值和传给yield的值是两码事!!

  yiedl的返回值就相当于return的返回值,这个值是要被传递出去的,而send()传递的值,是要被yield接受,供函数内部使用的的,明确这一点很重要的。那么上面的打印,就应该打印出yield的返回值,而传递进去的值则本保存在一个列表中。

三、实例

"""模拟:grep -rl 'root' /etc"""
import os
def deco(func):  # 用来开启协程def wrapper(*args,**kwargs):res = func(*args,**kwargs)next(res)  # res.seng(None)return resreturn wrapper
@deco
def search(target):while True:PATH = yieldg = os.walk(PATH)  # 获取PATH目录下的文件,文件夹for par_dir, _, files in g:  #迭代解包,取出当前目录路径和文件名for file in files:file_path = r'%s\%s' %(par_dir,file)  # 拼接文件的绝对路径target.send(file_path)  # 给下一个
@deco
def opener(target, pattern=None):while True:file_path = yieldwith open(file_path, encoding='utf-8') as f:target.send((file_path, f))  # 将文件路径和文件对象一起传递给下一个函数的yield,因为在打印路径时候,需要打印出文件路径,只有从这里传递下去
@deco
def cat(target):while True:filepath, f = yield # 这里接收opener传递进来的路径和文件对象for line in f:tag = target.send((filepath, line))  # 同样,也要传递文件路径,并且获取下一个函数grep的返回值,从而判断该文件是否重复读取了if tag:  # 如果为真,说明该文件读取过了,则执行退出循环break
@deco
def grep(target, pattern):tag = Falsewhile True:filepath, line = yield tag  # 接受两个值,并且设置返回值,這个返回值要传递给发送消息的send(),也就是cat()函数sendtag = Falseif pattern in line:  # 如果待匹配字符串在该行target.send(filepath)   # 把文件路径传递给pritertag = True   # 设置tag
@deco
def printer():while True:filename = yieldprint(filename)

调用方式:

PATH1 = r'D:\CODE_FILE\python\test'
search(opener(cat(grep(printer(), 'root')))).send(PATH1)

输出结果:

######找出了含有'root'的所有文件#######
D:\CODE_FILE\python\test\a.txt
D:\CODE_FILE\python\test\test1\c.txt
D:\CODE_FILE\python\test\test1\test2\d.txt

程序分析:

  有了上面的基础,我们来分析一下上述程序的执行。

  每一个函数之前都有一个@deco装饰器,这个装饰器用于开启协程。首先我们定义了一个search(),其内部有关键字yield,则search()是一个生成器。也就是说,我们可以通过send()给其传递一个值进去。search()函数的功能 是:获取一个文件的绝对路径,并将這个绝对路径通过send()方法,在传递给下个含有yield的函数,也就是下面的opener函数。opener的yield接受了search()传递进来的路径,并将其赋值给了file_path,然后我们根据這个路径,打开了一个文件,获取了一个文件对象f。然后我们在将這个文件对象send()给cat()函数,這个函数功能是读取文件中的内容,我们根据逐行读取文件内容,将每次读取到的内容,在send()给下一个函数,也就是grep(),這个函数功能是实现过滤操作。我们从上一个函数cat()接受到的每一行内容,在grep()函数里面进行过滤处理,如果有匹配到的过滤内容,那么我们将就过滤到的文件传递给下一个函数print(),该函数主要是打印出文件路径。也许,上述的描述内容你没看懂,下面看看这个程序的流程图:

根据上述流程,我们很清楚知道,send()传递给下一个函数的值。但是上述代码存在一个问题:如果待过滤的字符在一个文件中存在多个,而在读取文件的时候,我们是一行一行地读取,然后再传递给下一个函数。我们的目的是:过滤出包好pattern的文件,如果一个文件存在多个同样的pattern,那么就会输出多次同样的文件名。这无疑是浪费内存,要解决这中问题,我们可以通过yield的返回值来控制,每次读取时候的条件。具体实施,看上述代码注释。

python3之协程(2)---yield与send实现协程操作相关推荐

  1. 深入理解Python中的yield和send

    send方法和next方法唯一的区别是在执行send方法会首先把上一次挂起的yield语句的返回值通过参数设定,从而实现与生成器方法的交互. 但是需要注意,在一个生成器对象没有执行next方法之前,由 ...

  2. python3 协程 写法_理解Python的协程(Coroutine)

    由于GIL的存在,导致Python多线程性能甚至比单线程更糟. GIL: 全局解释器锁(英语:Global Interpreter Lock,缩写GIL),是计算机程序设计语言解释器用于同步线程的一种 ...

  3. 理解Python的协程机制-Yield

    根据PEP-0342 Coroutines via Enhanced Generators,原来仅仅用于生成器的yield关键字被扩展,成为Python协程实现的一部分.而之所以使用协程,主要是出于性 ...

  4. 学习笔记(35):Python网络编程并发编程-协程(yield,greenlet,gevent模块)

    立即学习:https://edu.csdn.net/course/play/24458/296457?utm_source=blogtoedu 协程(yield,greenlet,gevent) 1. ...

  5. 【Kotlin 协程】协程取消 ② ( CPU 密集型协程任务取消 | 使用 isActive 判定协程状态 | 使用 ensureActive 函数取消协程 | 使用 yield 函数取消协程 )

    文章目录 一.CPU 密集型协程任务取消 二.使用 isActive 判定当前 CPU 密集型协程任务是否取消 三.使用 ensureActive 自动处理协程退出 四.使用 yield 函数检查协程 ...

  6. python中协程与函数的区别_python 协程与go协程的区别

    进程.线程和协程 进程的定义: 进程,是计算机中已运行程序的实体.程序本身只是指令.数据及其组织形式的描述,进程才是程序的真正运行实例. 线程的定义: 操作系统能够进行运算调度的最小单位.它被包含在进 ...

  7. php7协程通信使用,PHP7下的协程实现

    原标题:PHP7下的协程实现 什么是协程 先搞清楚,什么是协程. 你可能已经听过『进程』和『线程』这两个概念. 进程就是二进制可执行文件在计算机内存里的一个运行实例,就好比你的.exe文件是个类,进程 ...

  8. 协程简史,一文讲清楚协程的起源、发展和实现

    /   今日科技快讯   / 北京时间10月5日下午,在瑞典首都斯德哥尔摩,瑞典皇家科学院宣布,将2022年诺贝尔化学奖授予美国化学家卡罗琳·贝尔托西.丹麦化学家摩顿·梅尔达尔和美国化学家卡尔·巴里· ...

  9. 进程、线程、协程 关于进程、线程、协程,有非常详细和丰富的博客或者学习资源,我不在此做赘述,我大致在此介绍一下这几个东西。 进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。

    进程.线程.协程 关于进程.线程.协程,有非常详细和丰富的博客或者学习资源,我不在此做赘述,我大致在此介绍一下这几个东西. 进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度. 线程 ...

最新文章

  1. 在百度工作是一种什么样的体验?
  2. DSP调试报错:OMAPL138 Connect to PRSC failed
  3. 【bzoj2694】Lcm 莫比乌斯反演+线性筛
  4. 高效、易用、功能强大的 api 管理平台(内附彩蛋)
  5. php curl 批量,PHP实现的curl批量请求操作
  6. 《JavaScript权威指南》学习笔记 第二天 下好一盘大棋
  7. Web前端笔记-移动端触屏移动视角(two.js)
  8. app软件测试是否强制升级_这些测试方法对于任何软件都必须是强制性的
  9. 安卓手机主题软件_手机看书神器!Amazon Kindleios、安卓软件
  10. QDataStream 多余字符的产生原因及消除方法
  11. 禅道备份功能_禅道备份处理
  12. QtQuick TableView 操作
  13. 邮箱如何开启pop3 smtp服务器,QQ邮箱开启POP3、SMTP方法
  14. 带宽叠加是什么意思?
  15. 十一则:程序员冷“笑话”据说只有真正的程序员才看得懂
  16. Mask OBB 论文学习笔记
  17. EasyBCD and UEFI
  18. Tensorlow 中文API:tf.zeros() tf.ones()tf.fill()tf.constant()
  19. win10打开蓝牙_Win10系统中蓝牙鼠标可以配对却无法使用应该如何解决?
  20. 流媒体技术介绍(中篇)

热门文章

  1. 体验Lambda表达式【理解】
  2. 缓存-分布式锁-Redisson-信号量测试
  3. 自定义线程池-线程池工作流程介绍
  4. 按指定格式拼接字符串
  5. 表贴电阻尺寸与什么有关_电路板上为什么会有0欧电阻这种东西?
  6. dns服务器zones文件,DNS服务安装与配置
  7. android分钟倒计时,Android 三十分钟倒计时
  8. 15怎么自己画元件_【技术】3.1(1) 先了解清楚了,才能画一个元件-成都单片机开发...
  9. 200924阶段一C++STL
  10. [置顶] mkdir函数-linux