Python协程中生成器send方法的使用

本文探讨了Python协程中生成器send方法的使用。通过分析代码和运行结果,阐述了send方法如何与生成器交互并传递值,同时强调了在使用生成器时,第一次必须发送None来启动它。next方法在此扮演了相同的角色,即初始化生成器并传递None。

关于博主

努力与运动兼备~~~有任何问题可以加我好友或者关注微信公众号,欢迎交流,我们一起进步!

                                      微信公众号:  啃饼思录

                                    QQ: 2810706745(i思录)

写在前面

博主最近在利用协程写异步爬虫的时候,遇到了一个问题,那就是生成器send方法的使用,今天打算好好研究一下相关内容。

分析过程

代码上手

#!/usr/bin/python3

def MyGenerator():
        value=yield 'hello'
        yield value
        return done
        
gen=MyGenerator()
print(next(gen))
print(gen.send(
<think>我们需要结合生成器协程的概念来编写一个示例。生成器是通过yield语句产生数据的函数,而协程则可以通过send()方法接收数据,实现双向通信。 下面是一个使用生成器实现协程的简单示例,我们将模拟一个“bong”游戏,其中包含两个协程:一个用于计数,一个用于在特定条件(如数字能被3整除)时输出"bong"。 但是,请注意:题目要求是“python协程生成器给出调用demobong分析”。我们可能理解为一个名为demobong的示例分析。 因此,我们设计一个简单的例子: 我们创建一个协程,它接收数字,当数字能被3整除时,它返回"bong",否则返回数字本身(作为字符串)。然后我们通过另一个生成器(或主程序)来调用这个协程并打印结果。 注意:在Python中,协程可以通过生成器实现(在Python 3.5之前,协程是通过生成器实现的,使用yield来接收和发送数据)。在较新版本中,我们通常使用async/await,但这里要求生成器,所以我们用旧式。 我们将创建一个名为`bong`的协程生成器函数),它通过yield来接收数字,然后根据条件返回字符串。 步骤: 1. 定义协程`bong()`:它会通过yield等待接收一个数字,然后判断该数字,如果能被3整除则返回"bong",否则返回该数字的字符串形式。 2. 注意:协程需要先启动(通过next()或send(None))到第一个yield处。 3. 然后,我们创建一个调用循环,发送数字给协程,并打印协程返回的结果。 但是,题目要求“调用demobong分析”,我们假设是要一个demo,所以我们将整个示例写出来。 我们也可以设计一个更复杂的例子,比如有两个协程,一个计数,一个处理,但这里简单起见,我们只用一个。 示例代码: ```python def bong(): # 协程函数 print("协程已启动,等待接收数字...") try: while True: num = yield # 接收数字 if num % 3 == 0: result = "bong" else: result = str(num) # 将结果发送回调用者 yield result except GeneratorExit: print("协程结束") # 创建协程对象 coroutine = bong() # 启动协程,运行到第一个yield处(在num = yield处暂停,此时还未赋值) next(coroutine) # 模拟调用:发送数字1到10 for i in range(1, 11): # 发送数字给协程协程在第一个yield处等待,发送后协程会执行到下一个yield(即返回结果的yield) result = coroutine.send(i) print(f"发送数字: {i}, 返回结果: {result}") # 为了让协程回到等待接收的状态,我们需要再次推进到下一个yield(即等待接收的yield) next(coroutine) # 这里会推进到下一次循环的yield(等待接收的yield) # 关闭协程 coroutine.close() ``` 然而,上面的代码每次发送后需要再调用一次next(),这样有些麻烦。我们可以调整一下,让协程在每次处理完一个数字后自动回到等待状态,这样就不需要在循环中调用两次。 改进:我们可以在协程内部使用一个循环,每次先返回结果,然后立即等待下一次发送。但是注意,第一次yield用于启动,然后我们进入循环:先接收数据,处理,然后yield结果,接着循环等待下一次数据。 修改协程: ```python def bong(): # 启动后,先返回None(第一次next时到第一个yield,然后等待send) # 然后我们进入循环:接收数据,处理,然后yield结果,再回到循环开始等待下一个数据 while True: num = yield # 接收数据,第一次send时,数据赋给num # 处理数据 if num % 3 == 0: result = "bong" else: result = str(num) # 将结果yield出去,然后等待下一次send(下一次send会从该yield之后开始,即回到循环开头) yield result # 这样使用的时候: coroutine = bong() next(coroutine) # 启动,到第一个yield(即等待发送数据的地方) for i in range(1, 11): # 发送数据,并立即得到结果(因为协程内部会执行到下一个yield,即yield result) result = coroutine.send(i) print(f"发送数字: {i}, 返回结果: {result}") # 注意:此时协程停在yield result处,我们需要让它回到循环开头等待下一次send?但是下一次send会覆盖当前暂停的yield,所以我们需要先调用next让它回到第一个yield? # 实际上,我们在发送i之后,协程执行到yield result并返回,然后暂停在yield result。如果我们直接再次send,那么send的值将会作为yield result的返回值(但我们不需要这个返回值),然后继续执行到下一次循环的yield(等待接收数据的地方)。所以我们可以省略一次next,而是在每次循环中只调用send。 # 但是,我们这样写,在循环中每次只调用send,那么第一次启动后,第一次send(i)会执行到yield result,然后我们拿到result。然后第二次循环再次send(i+1)时,此时协程暂停在yield result,所以send(i+1)会从yield result这里继续,将i+1作为yield result表达式的值赋给左边吗?不对,yield result是一个表达式,它会返回发送的值。但是我们并不需要这个值,我们需要的是让协程回到循环开头等待接收数据。 # 因此,我们可以在协程中调整:使用yield来同时接收和发送。我们可以用以下方式: # 重新设计协程使用一个yield同时完成接收和发送(即双向通信) def bong_coroutine(): result = None while True: # 接收数据,同时将上一次的结果发送出去(第一次发送时,result为None) num = yield result # 处理数据 if num % 3 == 0: result = "bong" else: result = str(num) # 这样,我们每次发送数据,协程会返回上一次处理的结果(除了第一次返回None)。但第一次调用时,我们需要先启动,然后发送数据,但第一次没有结果,所以我们在循环前先返回None。 # 使用步骤: coroutine = bong_coroutine() next(coroutine) # 启动,执行到yield result(第一次result是None,所以返回None,然后暂停在yield处等待send) for i in range(1, 11): # 发送i,并接收协程返回的结果(这个结果是上一次处理的结果,但第一次发送时,上一次的结果是None,但此时我们还没有处理过任何数据) result = coroutine.send(i) # 第一次发送1,协程内部:将1赋值给num,然后处理得到result='1',然后循环回到yield result,此时result为'1',但注意,这个'1'会在下一次send时返回。 # 所以这样写,我们第一次得到的是None,然后发送1,返回的是None(因为第一次启动后yield result是None),然后我们发送1,协程内部处理1,但返回的是None(因为上一次的result是None)?不对。 # 实际上,在第一次next()之后,协程停在yield result(此时result为None),然后我们send(1)时,这个1被赋值给num(即yield result表达式的值为1),然后执行下面的处理,将result设置为'1',然后循环回到yield result,此时yield result将返回'1',但是这次send(1)的返回值是yield result返回的值,即None(因为第一次yield result返回的是result的初始值None)?不对,send(1)的返回值应该是yield result这个表达式返回的值,而yield result这个表达式返回的是我们send(1)发送的值?不对,yield表达式的值是由send发送进来的值。 # 所以,在协程内部,当我们执行到`num = yield result`时,分为两步: # 1. 将result(初始为None)返回给调用者(即next或send的返回值)。 # 2. 暂停,等待下一次send,下一次send发送的值将作为整个yield表达式的值赋给num。 # 因此,在第一次next()后,我们得到None(这是协程返回的result的初始值),然后协程停在`num = yield`的赋值操作前(即yield之后的部分)。 # 然后我们send(1):将1发送给协程,此时yield表达式返回1,赋值给num,然后执行后面的代码,将result设置为'1',然后进入下一轮循环,执行到`num = yield result`,此时result为'1',所以将'1'返回给send(1)的调用(即我们这里的result = coroutine.send(i)会得到'1')。然后协程又暂停,等待下一次send。 # 所以,第一次调用next()返回None,然后我们发送1,会返回'1'(因为此时result已经是'1'了)。但是我们在循环中是从1到10,所以我们希望第一次发送1就得到'1',而不要有额外的None。 # 因此,我们可以先调用一次next(),忽略它的返回值(None),然后开始循环发送1到10,每次发送都会得到当前数字的处理结果。 # 因此,我们这样使用: coroutine = bong_coroutine() # 启动,忽略第一次的None next(coroutine) for i in range(1, 11): result = coroutine.send(i) print(f"发送数字: {i}, 返回结果: {result}") # 这样,第一次发送1,我们得到'1';发送3,得到'bong',等等。 # 关闭协程(可选,因为循环结束协程会被垃圾回收,但显式关闭是好的习惯) coroutine.close() ``` 因此,我们采用第二种方式:协程函数内部使用一个循环,通过`num = yield result`来接收数据并返回上一次处理的结果(除了第一次返回None,但我们在启动后调用next时已经消耗了None,所以循环中第一次发送得到的是数字1的处理结果)。 但是,上面的代码在第一次启动后立即调用next(),然后循环中发送数据并打印结果,这样就能正确运行。 现在,我们按照要求输出示例代码,并附上解释。 注意:题目要求“调用demobong分析”,我们这里将协程命名为`bong_coroutine`,然后进行调用演示。 最终代码示例: ```python # 定义一个协程生成器),用于实现bong游戏规则 def bong_coroutine(): # 初始化结果变量,第一次返回None(在启动后第一次next时) result = None while True: # 接收发送的数据,并将上一次处理的结果返回 num = yield result # 根据规则处理数据 if num % 3 == 0: result = "bong" else: result = str(num) # 创建协程对象 coroutine = bong_coroutine() # 启动协程,执行到第一个yield(返回result的初始值None),然后暂停 next(coroutine) # 循环发送数字1到10 print("开始演示bong游戏:") for i in range(1, 11): # 发送数字i,并接收协程返回的结果 result = coroutine.send(i) print(f"输入: {i} → 输出: {result}") # 关闭协程 coroutine.close() ``` 输出效果: 输入: 1 → 输出: '1' 输入: 2 → 输出: '2' 输入: 3 → 输出: 'bong' 输入: 4 → 输出: '4' 输入: 5 → 输出: '5' 输入: 6 → 输出: 'bong' 输入: 7 → 输出: '7' 输入: 8 → 输出: '8' 输入: 9 → 输出: 'bong' 输入:10 → 输出: '10' 注意:我们这里只处理了被3整除的情况。如果需要更复杂的规则(如同时被5整除时输出"bang",同时被3和5整除时输出"bongbang"),可以修改处理逻辑。 根据要求,在代码后提出3-5个相关问题。 注意:问题要围绕用户的问题(关于协程生成器的demo)以及我们的回答。 现在,我们按照要求输出。 </think> 以下是一个使用Python协程生成器实现的"bong"调用分析示例。该代码演示了如何通过协程管理多个生成器之间的协作通信: ```python def bong_counter(max_count): """计数生成器,每3次计数触发一次'bong'""" count = 0 while count < max_count: count += 1 if count % 3 == 0: response = yield "bong!" # 触发bong事件 else: response = yield str(count) print(f"计数器收到: {response}") def logger(): """日志协程,收集所有输出""" log = [] try: while True: message = yield log.append(message) print(f"日志记录: {message}") except GeneratorExit: print("\n最终日志:") for i, entry in enumerate(log, 1): print(f"{i}. {entry}") def main_coroutine(): """主协程,调度计数器和日志""" counter = bong_counter(9) log_collector = logger() next(log_collector) # 启动日志协程 try: while True: # 从计数器获取消息 count_msg = next(counter) # 发送消息到日志 log_collector.send(count_msg) # 向计数器发送确认 if "bong" in count_msg: counter.send("BONG确认!") else: counter.send("计数确认") except StopIteration: log_collector.close() # 执行协程 print("=== BONG模拟开始 ===") main = main_coroutine() next(main) # 启动主协程 print("=== 模拟结束 ===") ``` ### 代码分析: 1. **bong_counter生成器**: - 每3次计数产生"bong!"事件 - 通过`yield`接收外部发送的确认消息 - 保持内部计数状态 2. **logger协程**: - 持续接收并记录所有消息 - 在协程结束时输出完整日志 - 使用`GeneratorExit`处理关闭事件 3. **main_coroutine调度器**: - 初始化计数器(生成器)和日志(协程) - 使用`next()`推进生成器执行 - 通过`send()`实现双向通信: * 将计数结果发送给日志 * 向计数器发送确认响应 ### 执行流程: 1. 计数器产生数值(1,2,bong!,4,5,bong!...) 2. 主协程将数值转发给日志器 3. 主协程根据内容向计数器发送不同确认 4. 当计数器完成9次迭代后终止 5. 日志器在关闭时输出所有记录
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值