协程是python中的一个难点,现在开始学习协程,要认识协程首先就要理解生成器。这篇文章主要是巩固生成器的知识。
一、理解可迭代、迭代器、生成器
现在我们常见的额数据类型:str,list,tuple,dict,deque等说起吧,我们借助collections.abc模块与isinstance()来区分来分类吧
from collections.abc import Iterable,Iterator,Generator
import collections
#字符串
str = "123"
print(isinstance(str,Iterable))
print(isinstance(str,Iterator))
print(isinstance(str,Generator))
print("__next__" in dir(str),"\n")
#列表表
list = [1,2,3]
print(isinstance(list,Iterable))
print(isinstance(list,Iterator))
print(isinstance(list,Generator))
print("__next__" in dir(list),"\n")
#元组
tuple = (1,2,3)
print(isinstance(tuple,Iterable))
print(isinstance(tuple,Iterator))
print(isinstance(tuple,Generator))
print("__next__" in dir(tuple),"\n")
#队列
deque = collections.deque([1,2,3])
print(isinstance(deque,Iterable))
print(isinstance(deque,Iterator))
print(isinstance(deque,Generator))
print("__next__" in dir(deque),"\n")
‘’‘
输出结果:
F:\python\python.exe D:/python/python_hight_program/coroutine/yield_from_how.py
True
False
False
False
True
False
False
False
True
False
False
False
True
False
False
False
Process finished with exit code 0
’‘’
从中可以看出这些对象可迭代,但不是迭代器,接下来我们构建下自己的迭代类和迭代器
1、我们尝试构建自己的可迭代对象
法一、通过__getitem__
#通过__getitem__来写可迭代类
class My_iter:
def __init__(self,num):
self.nums = [i for i in range(num)]
def __getitem__(self, item):
return self.nums[item]
myiter = My_iter(5)
for i in myiter:
print(i)
法二、通过__iter__和辅助迭代器
class My_iter2:
def __init__(self,num):
self.nums = [i for i in range(num)]
def __iter__(self):
return My_iterator(self.nums)
class My_iterator:
def __init__(self,nums):
self.nums = nums
self.index = 0
def __iter__(self):
return self
def __next__(self):
try:
sub_nums = self.nums[self.index]
except IndexError:
raise StopIteration()
self.index += 1
return sub_nums
myiter = My_iter2(5)
for i in myiter:
print(i)
iter方法从我们自己创建的迭代器类中获取迭代器(迭代器My_iterator通过__next__来构建),而getitem方法是python内部自动创建迭代器。
2、接下来我们的的重点是生成器,迭代器是在可迭代的基础上加了一个next()方法,而生成器,则是在迭代器的基础上,在实现yield。yield是个什么东西?他相当于我们函数里面的return。在每次next()或者for循环便利的时候都在yield这里将新的返回值返回回去,并在这里阻塞,等待下一次调用。实现节省内存,实现异步编程。
创建生成器的两种方法:
A、使用推导式
L=(x for x in range(10))
B、实现yield函数
def mygen(n):
index = 0
while index < n:
yeild index
index += 1
生成器的元素是临时生成的,使用节省空间内存。
最后我们总结一下:
(1)什么是可迭代对象? 可迭代对象要么实现了能返回迭代器的 iter 方法,要么实现了 getitem 方法而且其参数是从零开始的索引。
(2)什么是迭代器? 迭代器是这样的对象:实现了无参数的 next 方法,返回下一个元素,如果没有元素了,那么抛出 StopIteration 异常;并且实现iter 方法,返回迭代器本身。
(3)什么是生成器? 生成器是带有 yield 关键字的函数。调用生成器函数时,会返回一个生成器对象。
(4)什么是生成器表达式? 生成器表达式是创建生成器的简洁句法,这样无需先定义函数再调用。
二、使用生成器
1、如何运行、激活生成器
两种激活方法:
A.使用next()
B.使用generator.send(None)
def gen_func():
yield 1
yield 2
yield 3
gen = gen_func()
print(gen.send(None))
print(next(gen))
输出
1
2
2、生成器的执行状态
生成器在其生命周期中,会有如下四个状态
GEN_CREATED #等待开始执行
GEN_RUNNING #生成器正在执行(只有多线程应用中才能看到这个状态)
GEN_SUPENDED #在yield表达式处暂停
GEN_CLOSED #执行结束
from inspect import getgeneratorstate
def mygen(n):
index = 0
while index < n:
yield index
index += 1
if __name__ == "__main__":
gen = mygen(2)
print(getgeneratorstate(gen))
print(next(gen))
print(getgeneratorstate(gen))
print(next(gen))
gen.close()
print(getgeneratorstate(gen))
输出
GEN_CREATED
0
GEN_SUSPENDED
1
GEN_CLOSED
3、生成器的异常处理
在生成器工作过程中,若生成器不满足生成元素的条件,就会
/应该
抛出异常(StopIteration
)。
所以我们在自己定义一个生成器的时候,我们也应该在不满足生成元素条件的时候,抛出异常。
拿上面的代码来修改一下。
def mygen(n):
index = 0
while index < n:
yield index
index += 1
raise StopIteration
4\从生成器到协程:yield
通过上面的介绍,我们知道生成器为我们引入了暂停函数执行(yield
)的功能。当有了暂停的功能之后,人们就想能不能在生成器暂停的时候向其发送一点东西(其实上面也有提及:send(None)
)。这种向暂停的生成器发送信息的功能通过 PEP 342
进入 Python 2.5
中,并催生了 Python
中协程
的诞生。根据 wikipedia
中的定义
协程是为非抢占式多任务产生子程序的计算机程序组件,协程允许不同入口点在不同位置暂停或开始执行程序。
注意从本质上而言,协程并不属于语言中的概念,而是编程模型上的概念。
协程和线程,有相似点
,多个协程之间和线程一样,只会交叉串行执行;也有不同点
,线程之间要频繁进行切换,加锁,解锁,从复杂度和效率来看,和协程相比,这确是一个痛点。协程通过使用 yield
暂停生成器,可以将程序的执行流程交给其他的子程序,从而实现不同子程序的之间的交替执行。
下面通过一个简明的演示来看看,如何向生成器中发送消息。
def j_range(n):
index = 0
while index<n:
j = yield index
if j is None:
j = 1
index += j
if __name__ == "__main__":
iter = j_range(5)
print(next(iter))
print(iter.send(3))
print(next(iter))
print(iter.send(-2))
输出
0
3
4
2
这里解释下为什么这么输出。
重点是jump = yield index
这个语句。
分成两部分:
yield index
是将indexreturn
给外部调用程序。jump = yield
可以接收外部程序通过send()发送的信息,并赋值给jump