python3之协程(2)---yield与send实现协程操作
原文链接: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实现协程操作相关推荐
- 深入理解Python中的yield和send
send方法和next方法唯一的区别是在执行send方法会首先把上一次挂起的yield语句的返回值通过参数设定,从而实现与生成器方法的交互. 但是需要注意,在一个生成器对象没有执行next方法之前,由 ...
- python3 协程 写法_理解Python的协程(Coroutine)
由于GIL的存在,导致Python多线程性能甚至比单线程更糟. GIL: 全局解释器锁(英语:Global Interpreter Lock,缩写GIL),是计算机程序设计语言解释器用于同步线程的一种 ...
- 理解Python的协程机制-Yield
根据PEP-0342 Coroutines via Enhanced Generators,原来仅仅用于生成器的yield关键字被扩展,成为Python协程实现的一部分.而之所以使用协程,主要是出于性 ...
- 学习笔记(35):Python网络编程并发编程-协程(yield,greenlet,gevent模块)
立即学习:https://edu.csdn.net/course/play/24458/296457?utm_source=blogtoedu 协程(yield,greenlet,gevent) 1. ...
- 【Kotlin 协程】协程取消 ② ( CPU 密集型协程任务取消 | 使用 isActive 判定协程状态 | 使用 ensureActive 函数取消协程 | 使用 yield 函数取消协程 )
文章目录 一.CPU 密集型协程任务取消 二.使用 isActive 判定当前 CPU 密集型协程任务是否取消 三.使用 ensureActive 自动处理协程退出 四.使用 yield 函数检查协程 ...
- python中协程与函数的区别_python 协程与go协程的区别
进程.线程和协程 进程的定义: 进程,是计算机中已运行程序的实体.程序本身只是指令.数据及其组织形式的描述,进程才是程序的真正运行实例. 线程的定义: 操作系统能够进行运算调度的最小单位.它被包含在进 ...
- php7协程通信使用,PHP7下的协程实现
原标题:PHP7下的协程实现 什么是协程 先搞清楚,什么是协程. 你可能已经听过『进程』和『线程』这两个概念. 进程就是二进制可执行文件在计算机内存里的一个运行实例,就好比你的.exe文件是个类,进程 ...
- 协程简史,一文讲清楚协程的起源、发展和实现
/ 今日科技快讯 / 北京时间10月5日下午,在瑞典首都斯德哥尔摩,瑞典皇家科学院宣布,将2022年诺贝尔化学奖授予美国化学家卡罗琳·贝尔托西.丹麦化学家摩顿·梅尔达尔和美国化学家卡尔·巴里· ...
- 进程、线程、协程 关于进程、线程、协程,有非常详细和丰富的博客或者学习资源,我不在此做赘述,我大致在此介绍一下这几个东西。 进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。
进程.线程.协程 关于进程.线程.协程,有非常详细和丰富的博客或者学习资源,我不在此做赘述,我大致在此介绍一下这几个东西. 进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度. 线程 ...
最新文章
- 在百度工作是一种什么样的体验?
- DSP调试报错:OMAPL138 Connect to PRSC failed
- 【bzoj2694】Lcm 莫比乌斯反演+线性筛
- 高效、易用、功能强大的 api 管理平台(内附彩蛋)
- php curl 批量,PHP实现的curl批量请求操作
- 《JavaScript权威指南》学习笔记 第二天 下好一盘大棋
- Web前端笔记-移动端触屏移动视角(two.js)
- app软件测试是否强制升级_这些测试方法对于任何软件都必须是强制性的
- 安卓手机主题软件_手机看书神器!Amazon Kindleios、安卓软件
- QDataStream 多余字符的产生原因及消除方法
- 禅道备份功能_禅道备份处理
- QtQuick TableView 操作
- 邮箱如何开启pop3 smtp服务器,QQ邮箱开启POP3、SMTP方法
- 带宽叠加是什么意思?
- 十一则:程序员冷“笑话”据说只有真正的程序员才看得懂
- Mask OBB 论文学习笔记
- EasyBCD and UEFI
- Tensorlow 中文API:tf.zeros() tf.ones()tf.fill()tf.constant()
- win10打开蓝牙_Win10系统中蓝牙鼠标可以配对却无法使用应该如何解决?
- 流媒体技术介绍(中篇)
热门文章
- 体验Lambda表达式【理解】
- 缓存-分布式锁-Redisson-信号量测试
- 自定义线程池-线程池工作流程介绍
- 按指定格式拼接字符串
- 表贴电阻尺寸与什么有关_电路板上为什么会有0欧电阻这种东西?
- dns服务器zones文件,DNS服务安装与配置
- android分钟倒计时,Android 三十分钟倒计时
- 15怎么自己画元件_【技术】3.1(1) 先了解清楚了,才能画一个元件-成都单片机开发...
- 200924阶段一C++STL
- [置顶] mkdir函数-linux