python yield、yield from与协程

从生成器到协程

协程是指一个过程,这个过程与调用方协作,产出由调用方提供的值。生成器的调用方可以使用 .send(...)方法发送数据,发送的数据会成为yield表达式的值。因此,生成器可以作为协程使用。

从句法上看,生成器与协程都是包含yield关键字的函数。但是,在协程中,yield通常出现在表达式的右边(* = yield *),可以产出值一可以不产出(yield关键字后边没有表达式,产出None)。

协程有四个状态:

GEN_CREATED:等待开始执行

GEN_RUNNING:正在执行(只有在多线程应用或生成器对象自身调用getgeneratorstate函数可以看到此状态)

GEN_SUSPENDED:在yield表达式处阻塞

GEN_CLOSED:执行结束

使用inspect.getgeneratorstate(...)函数可以查看当前协程的状态。

使用协程的基本步骤为:

  • 创建协程对象
  • 调用next函数,激活协程
  • 调用 .send(...)方法,推动协程执行并产出

一个累积求和的协程示例如下:

如上图示例所示,协程中产出的值会返回给调用方,同时,通过yield将调用方传入的参数赋值给yield表达式左边的变量,并推动协程继续执行。

终止协程和异常处理

因为协程使用生成器函数定义,因此遵循生成器的特性,当协程执行到定义体末尾时,会抛出StopIteration异常。如果协程在执行过程中发生了未处理的异常,协程会终止运行并将异常抛出,此时,试图重新激活协程会抛出StopIteration异常。代码示例:

示例代码中,依然使用累积求和的协程,调用时因为传入了字符串参数,导致协程因TpyeError异常而终止,再次试图调用时,抛出了StopIteration异常。

调用方可以通过调用生成器对象 .throw(exc_type[, exc_value[, traceback]])方法,致使生成器在阻塞的yield表达式处抛出指定的异常。如果生成器处理了抛出的异常,代码会向前执行到下一个yield表达式,产出的表达式会成为 .throw()方法的返回值;如果生成器没有处理抛出的异常,异常会向上冒泡,传到调用方的上下文中。代码示例:

上图示例代码中,协程对TypeError进行了处理,所以当调用方将TpyeError异常发给协程时没有终止;而当调用方将ValueError发给协程时,由于没有处理,协程终止并将异常向上抛给调用方处理,调用方虽然捕获了该异常,但试图再次调用协程时,由于协程已终止,故抛出了StopIteration异常。

调用方可以通过生成器对象的 .close()方法,致使生成器在阻塞的yield表达式处抛出GeneratorExit异常。如果生成器没有处理这个异常,或者抛出了StopIteration异常(通常指运行到程序结尾),调用方不会报错。代码示例:

上图示例代码中,调用 .close()方法后,调用方没有报错,协程终止且返回值为None,试图再次激活协程对象时,会抛出StopIteration异常。

需要注意的是:如果在协程中捕获了GeneratorExit异常,会导致RuntimeError;如果使用 .throw()方法直接将GeneratorExit异常发给协程,调用方会报错并导致GeneratorExit异常。

让协程返回值

有些协程不会产出值,而是在执行结束后返回一个值,而为了返回这个值,协程必须正常终止。代码示例:

上图示例中,协程不再产出值,通过send(None)结束协程,代码执行到最后触发StopIteration异常,而返回值作为StopIteration异常的一个属性返回给调用方。

yield from

yield from是全新的语言结构,多用于嵌套生成器。其主要功能是开辟一个双向通道,把最外层的调用方与最内层的子生成器连接起来,这样二者可以发送/产出值,还可以直接传入异常,而不用在位于中间层的协程中添加大量处理异常的代码。简言之即yield from可以方便的实现生成器嵌套调用并自动处理大部分异常。

理解yield from首先要理解三个概念:

  • 调用方:指委派生成器的客户端代码
  • 委派生成器:包含yield from <iterable>表达式的生成器函数
  • 子生成器:从yield from表达式中<iterable>部分获取的生成器

典型的调用逻辑为:客户端代码(调用方)调用委派生成器对象,委派生成器在yield from表达式处阻塞,此时调用方与子生成器之间的双向通道打开,调用方可以直接把数据发给子生成器,子生成器把产出的值发给调用方。子生成器执行结束,解释器抛出StopIteration异常,并把返回值附加到异常对象上,此时委派生成器恢复执行。委派生成器yield from语句自动处理子生成器抛出的StopIteration异常及附加在异常对象上的返回值。代码示例如下:

注意:委派生成器执行结束时也会抛出StopIteration异常,这里使用了永久循环+全局变量(不推荐)的方式避免委派生成器退出引发StopIteration异常且使客户端能够拿到子生成器返回的结果。实际应用中应视情况进行异常处理。子生成器StopIteration之外未处理的异常会向上冒泡传给委派生成器处理,yield from表达式的值是子生成器终止时传给StopIteration异常的第一个参数。python3.5以后引入了await关键字来替代yield from,使代码更加简洁清晰。

以上。

原文地址:https://www.cnblogs.com/sheshouxin/p/10463898.html

时间: 2024-10-09 06:50:17

python yield、yield from与协程的相关文章

python线程、进程和协程

链接:http://www.jb51.net/article/88825.htm 引言 解释器环境:python3.5.1 我们都知道python网络编程的两大必学模块socket和socketserver,其中的socketserver是一个支持IO多路复用和多线程.多进程的模块.一般我们在socketserver服务端代码中都会写这么一句: server = socketserver.ThreadingTCPServer(settings.IP_PORT, MyServer) Threadi

python第五天:协程,匿名函数,递归函数,模块导入,re模块

上节回顾 函数对象 函数可以被当成数据来传递 def func(): pass 1.可以被引用.f=fun 2.可以当做参数传递给另外一个函数 3.可以作为函数的返回值 4.可以当做容器类型的元素 比如用户有10多个参数,我们不需要写10多个if判断.可以写一个字典. dic = {'func1':func1,'func2':func2} 调用个时候就用dic['func1']() 函数的嵌套 函数的嵌套可以分为两种:嵌套定义和嵌套调用. 嵌套调用:函数的执行过程中调用其他的函数,可以更加细化的

python线程、进程、协程

进程与线程之间的定义 计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源的管理和分配.任务的调度. 程序是运行在系统上的具有某种功能的软件,比如说浏览器,音乐播放器等. 每次执行程序的时候,都会完成一定的功能,比如说浏览器帮我们打开网页,为了保证其独立性,就需要一个专门的管理和控制执行程序的数据结构——进程控制块. 进程就是一个程序在一个数据集上的一次动态执行过程. 进程一般由程序.数据集.进程控制块三

python进程、线程、协程

进程与线程的历史 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源的管理和分配.任务的调度. 程序是运行在系统上的具有某种功能的软件,比如说浏览器,音乐播放器等. 每次执行程序的时候,都会完成一定的功能,比如说浏览器帮我们打开网页,为了保证其独立性,就需要一个专门的管理和控制执行程序的数据结构--进程控制块. 进程就是一个程序在一个数据集上的一次动态执行过程. 进程一般由程序.数据集.进程控

Python进程、线程、协程详解

进程与线程的历史 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源的管理和分配.任务的调度. 程序是运行在系统上的具有某种功能的软件,比如说浏览器,音乐播放器等. 每次执行程序的时候,都会完成一定的功能,比如说浏览器帮我们打开网页,为了保证其独立性,就需要一个专门的管理和控制执行程序的数据结构——进程控制块. 进程就是一个程序在一个数据集上的一次动态执行过程. 进程一般由程序.数据集.进程控

python进程、线程、协程以及几种自定义线程池

Python线程 Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元. #!/usr/bin/env python # -*- coding:utf-8 -*- import threading import time    def show(arg):     time.sleep(1)     print 'thread'+str(arg)    for i in range(10):     t = threading.Thread(target=show, args

Python的异步编程[0] -&gt; 协程[1] -&gt; 使用协程建立自己的异步非阻塞模型

使用协程建立自己的异步非阻塞模型 接下来例子中,将使用纯粹的Python编码搭建一个异步模型,相当于自己构建的一个asyncio模块,这也许能对asyncio模块底层实现的理解有更大的帮助.主要参考为文末的链接,以及自己的补充理解. 完整代码 1 #!/usr/bin/python 2 # ============================================================= 3 # File Name: async_base.py 4 # Author: L

4月28日 python学习总结 线程与协程

一. 异步与回调机制 问题: 1.任务的返回值不能得到及时的处理,必须等到所有任务都运行完毕才能统一进行处理 2.解析的过程是串行执行的,如果解析一次需要花费2s,解析9次则需要花费18s 解决一: (线程实现异步,回调解析结果) from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor from threading import current_thread import requests import os i

Python并发编程(线程队列,协程,Greenlet,Gevent)

线程队列 线程之间的通信我们列表行不行呢,当然行,那么队列和列表有什么区别呢? queue队列 :使用import queue,用法与进程Queue一样 queue is especially useful in threaded programming when information must be exchanged safely between multiple threads. class queue.Queue(maxsize=0) #先进先出 import queue #不需要通过

python进程,线程,协程

参考别人的博客:https://www.cnblogs.com/whatisfantasy/p/6440585.html 线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务 创建线程的两种方法 # 方法1 直接调用 import threading import time def sayhi(num): print("running on number:%s&