可迭代 可迭代对象,是其内部实现了,__iter__
这个魔术方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 In [18 ]: a = [1 , 2 , 3 , 4 , 5 ] In [19 ]: dir(a) Out[19 ]: ['__add__' , '__class__' , '__contains__' , '__delattr__' , '__delitem__' , '__dir__' , '__doc__' , '__eq__' , '__format__' , '__ge__' , '__getattribute__' , '__getitem__' , '__gt__' , '__hash__' , '__iadd__' , '__imul__' , '__init__' , '__init_subclass__' , '__iter__' , '__le__' , '__len__' , '__lt__' , '__mul__' , '__ne__' , '__new__' , '__reduce__' , '__reduce_ex__' , '__repr__' , '__reversed__' , '__rmul__' , '__setattr__' , '__setitem__' , '__sizeof__' , '__str__' , '__subclasshook__' , 'append' , 'clear' , 'copy' , 'count' , 'extend' , 'index' , 'insert' , 'pop' , 'remove' , 'reverse' , 'sort' ]
迭代器 可以使用 iter 直接将一个可迭代的对象转换为 迭代器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 In [20 ]: b = iter(a) In [21 ]: type(b) Out[21 ]: list_iterator In [22 ]: next(b) Out[22 ]: 1 In [23 ]: next(b) Out[23 ]: 2 In [24 ]: next(b) Out[24 ]: 3 In [25 ]: next(b) Out[25 ]: 4
对比可迭代对象,迭代器
其实就只是多了一个函数而已。就是__next__()
,我们可以不再使用for
循环来间断获取元素值。而可以直接使用next()方法来实现。
可以通过,dir()
方法来查看是否有__next__
来判断一个变量是否是迭代器的 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 In [26 ]: dir(b) Out[26 ]: ['__class__' , '__delattr__' , '__dir__' , '__doc__' , '__eq__' , '__format__' , '__ge__' , '__getattribute__' , '__gt__' , '__hash__' , '__init__' , '__init_subclass__' , '__iter__' , '__le__' , '__length_hint__' , '__lt__' , '__ne__' , '__new__' , '__next__' , '__reduce__' , '__reduce_ex__' , '__repr__' , '__setattr__' , '__setstate__' , '__sizeof__' , '__str__' , '__subclasshook__' ]
生成器 前面我们说,迭代器,是在可迭代的基础上,加了一个next()方法。 而生成器,则是在迭代器的基础上(可以用for循环,可以使用next()
),再实现了yield
。 yield
是什么东西呢,它相当于我们函数里的 return。在每次next(),或者for遍历的时候,都会 yield 这里将新的值返回回去,并在这里阻塞,等待下一次的调用。
创建生成器
使用列表生成式
1 2 L = (x * x for x in range(10 ))
实现 yield 的函数
1 2 3 4 5 6 7 8 9 def mygen (n) : now = 0 while now < n: yield now now += 1 if __name__ == '__main__' : gen = mygen(10 )
可迭代对象和迭代器,是将所有的值都生成存放在内存中,而生成器
则是需要元素才临时生成,节省时间,节省空间
运行生成器
使用next()
使用generator.send(None)
In [51]: next(a) Out[51]: 0
In [52]: next(a) Out[52]: 1
In [53]: a.send(None) Out[53]: 2
In [54]: a.send(None) Out[54]: 3 1 2 3 4 5 6 7 8 9 10 #### 生成器生命周期 ```python GEN_CREATED # 等待开始执行 GEN_RUNNING # 解释器正在执行(只有在多线程应用中才能看到这个状态) GEN_SUSPENDED # 在yield表达式处暂停 GEN_CLOSED # 执行结束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 In [56 ]: from inspect import getgeneratorstate In [57 ]: c = mygen(10 ) In [58 ]: getgeneratorstate(c) Out[58 ]: 'GEN_CREATED' In [59 ]: next(c) Out[59 ]: 0 In [60 ]: getgeneratorstate(c) Out[60 ]: 'GEN_SUSPENDED' In [61 ]: next(c) Out[61 ]: 1 In [62 ]: getgeneratorstate(c) Out[62 ]: 'GEN_SUSPENDED'
协程 yield 通过上面的介绍,我们知道生成器为我们引入了暂停函数执行(yield
)的功能。当有了暂停的功能之后,人们就想能不能在生成器暂停的时候向其发送一点东西(其实上面也有提及:send(None)
)。这种向暂停的生成器发送信息的功能通过 PEP 342
进入 Python 2.5
中,并催生了 Python
中协程
的诞生。协程通过使用 yield
暂停生成器,可以将程序的执行流程交给其他的子程序,从而实现不同子程序的之间的交替执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def jumping_range (N) : index = 0 while index < N: jump = yield index if jump is None : jump = 1 index += jump if __name__ == '__main__' : itr = jumping_range(5 ) print(next(itr)) print(itr.send(2 )) print(next(itr)) print(itr.send(-1 ))
yield index
是将index return
给外部调用程序。jump = yield
可以接收外部程序通过send()发送的信息,并赋值给jump
输出:
yield from 用法 yield from
是在Python3.3才出现的语法。所以这个特性在Python2中是没有的。
yield from
后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器。
我们可以用一个使用yield
和一个使用yield from
的例子来对比看下 :
使用yield
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 astr='ABC' alist=[1 ,2 ,3 ] adict={"name" :"wangbm" ,"age" :18 } agen=(i for i in range(4 ,8 )) def gen (*args, **kw) : for item in args: for i in item: yield i new_list=gen(astr, alist, adict, agen) print(list(new_list))
使用yield from
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 astr='ABC' alist=[1 ,2 ,3 ] adict={"name" :"wangbm" ,"age" :18 } agen=(i for i in range(4 ,8 )) def gen (*args, **kw) : for item in args: yield from item new_list=gen(astr, alist, adict, agen) print(list(new_list))
由上面两种方式对比,可以看出,yield from 后面加上可迭代对象,生成器等。他可以把可迭代对象里的每个元素一个一个的 yield 出来,对比 yield 来说代码更加简洁,结构更加清晰。
当 yield from
后面加上一个生成器后,就实现了生成的嵌套。当然实现生成器的嵌套,并不是一定必须要使用yield from
,而是使用yield from
可以让我们避免让我们自己处理各种料想不到的异常,而让我们专注于业务代码的实现。
1、调用方
:调用委派生成器的客户端(调用方)代码
2、委托生成器
:包含yield from表达式的生成器函数
3、子生成器
:yield from后面加的生成器函数
这个例子,是实现实时计算平均值的。 比如,第一次传入10,那返回平均数自然是10. 第二次传入20,那返回平均数是(10+20)/2=15 第三次传入30,那返回平均数(10+20+30)/3=20 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 def average_gen () : total = 0 count = 0 average = 0 while True : new_num = yield average count += 1 total += new_num average = total/count def proxy_gen () : while True : yield from average_gen() def main () : calc_average = proxy_gen() next(calc_average) print(calc_average.send(10 )) print(calc_average.send(20 )) print(calc_average.send(30 )) if __name__ == '__main__' : main()
委托生成器的作用是 :在调用方与子生成器之间建立一个双向通道
。所谓的双向通道是什么意思呢? 调用方可以通过send()
直接发送消息给子生成器,而子生成器 yield 的值,也是直接返回给调用方。你可能会经常看到有些代码,还可以在yield from
前面看到可以赋值。这是什么用法? 你可能会以为,子生成器 yield 回来的值,被委托生成器给拦截了,但其实并不是这样,因为我们之前说了,委托生成器,只起一个桥梁作用,它建立的是一个双向通道
,它并没有权利也没有办法,对子生成器 yield 回来的内容做拦截。
为了解释这个用法,我还是用上述的例子,并对其进行了一些改造。添加了一些注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 def average_gen () : total = 0 count = 0 average = 0 while True : new_num = yield average if new_num is None : break count += 1 total += new_num average = total/count return total,count,average def proxy_gen () : while True : total, count, average = yield from average_gen() print("计算完毕!!\n总共传入 {} 个数值, 总和:{},平均数:{}" .format(count, total, average)) def main () : calc_average = proxy_gen() next(calc_average) print(calc_average.send(10 )) print(calc_average.send(20 )) print(calc_average.send(30 )) calc_average.send(None ) if __name__ == '__main__' : main()
输出
1 2 3 4 5 10.0 15.0 20.0 计算完毕!! 总共传入 3 个数值, 总和:60,平均数:20.0
到底yield from 有什么过人之处,让我们非要用它不可, 如果我们去掉委托生成器,而直接调用子生成器。那我们就需要把代码改成像下面这样,我们需要自己捕获异常并处理。而不像使yield from
那样省心。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 def average_gen () : total = 0 count = 0 average = 0 while True : new_num = yield average if new_num is None : break count += 1 total += new_num average = total/count return total,count,average def main () : calc_average = average_gen() next(calc_average) print(calc_average.send(10 )) print(calc_average.send(20 )) print(calc_average.send(30 )) try : calc_average.send(None ) except StopIteration as e: total, count, average = e.value print("计算完毕!!\n总共传入 {} 个数值, 总和:{},平均数:{}" .format(count, total, average)) if __name__ == '__main__' : main()