Python 中 yield 的作用:http://youchen.me/2017/02/10/Python-What-does-yield-do/#
Python 生成器详解:http://codingpy.com/article/python-generator-notes-by-kissg/#generator
Python yield与实现:http://www.cnblogs.com/coder2012/p/4990834.html
1. 迭代(iteration)、迭代器(iterator)、generator(生成器)
1.1 迭代
为了搞清楚 yield 是用来做什么的,首先得知道 Python 中 generator(生成器) 的相关概念,要理解 generator(生成器) ,的先从 迭代(iteration) 与 迭代器(iterator) 讲起。
迭代是重复反馈过程的活动,其目的通常是为了接近并到达所需的目标或结果。每一次对过程的重复被称为一次“迭代”,而每一次迭代得到的结果会被用来作为下一次迭代的初始值。———— 以上是 维基百科 对迭代的定义。
在 Python中,迭代 通常是通过 for ... in ...
来完成的,而且只要是 可迭代对象(iterable)
,都能进行迭代。
当你创建一个了列表,你可以逐个遍历列表中的元素,而这个过程便叫做 迭代 。
>>> mylist = [1, 2, 3]
>>> for i in mylist:
... print(i)
1
2
3
而 mylist
是一个可迭代对象。当你使用列表推导式的时候,创建了一个列表,他也是可迭代对象:
>>> mylist = [x*x for x in range(3)]
>>> for i in mylist:
... print(i)
0
1
4
所有能够接受 for...in...
操作的对象都是 可迭代对象,如:列表、字符串、文件等。这些可迭代对象用起来都十分顺手。 因为你可以按照你的想法去访问它们,但是你把所有数据都保存在了内存中,而当你有大量数据的时候这可能并不是你想要的结果。
1.2 可迭代对象(iterable) 与 迭代器(iterator)
这里简单讲下 可迭代对象(iterable)
与 迭代器(iterator
):
1.可迭代对象(iterable)
是实现了__iter__()
方法的对象。更确切的说,是container.__iter__()
方法,该方法返回的是的一个iterator
对象,因此iterable
是你可以从其获得iterator
的对象。对于iterable,
重点关注的是:它是一个能一次返回一个成员的对象(iterable is an object capable of returning its members one at a time),一些iterable
将所有值都存储在内存中,比如list,
而另一些并不是这样,比如我们下面将讲到的iterator
.- 2. 迭代器(
iterator
) 是实现了iterator.__iter__()
和iterator.__next__()
方法的对象。iterator.__iter__()
方法返回的是iterator
对象本身。根据官方的说法,正是这个方法,实现了for ... in ...
语句。而iterator.__next__()
是iterator
区别于iterable
的关键,它允许我们 显式 地获取一个元素。当调用next()
方法时,实际上产生了2个操作:
1. 更新 iterator 状态,令其指向后一项,以便下一次调用
2. 返回当前结果
如果你学过 C++
,它其实跟指针的概念很像(如果你还学过链表的话,或许能更好地理解)。
正是 __next__()
,使得iterator
能在每次被调用时,返回一个单一的值(有些教程里,称为一边循环,一边计算,我觉得这个说法不是太准确。但如果这样的说法有助于你的理解,我建议你就这样记),从而极大的节省了内存资源。另一点需要格外注意的是,iterator
是消耗型的,即每一个值被使用过后,就消失了。因此,你可以将以上的操作2理解成pop
。对iterator
进行遍历之后,其就变成了一个空的容器了,但不等于None
哦。因此,若要重复使用iterator
,利用list()
方法将其结果保存起来是一个不错的选择。
我们通过代码来感受一下。
>>> from collections import Iterable, Iterator
>>> a = [1,2,3] # 众所周知,list是一个iterable
>>> b = iter(a) # 通过iter()方法,得到iterator,iter()实际上调用了__iter__(),此后不再多说
>>> isinstance(a, Iterable)
True
>>> isinstance(a, Iterator)
False
>>> isinstance(b, Iterable)
True
>>> isinstance(b, Iterator)
True
# 可见,iterable是iterator,但iterator不一定是iterable
iterator 是消耗型的,用一次少一次。对 iterator 进行遍历,iterator就空了!
# iterator是消耗型的,用一次少一次.对iterator进行遍历,iterator就空了!
>>> c = list(b)
>>> c
[1, 2, 3]# c 已经遍历过了,所以 d 就为 空 了
>>> d = list(b)
>>> d
[]
空的 iterator 并不等于 None。
>>> if b:
... print(1)
...
1
>>> if b == None:
... print(1)
...
再来感受一下 next()
>>> e = iter(a)
>>> next(e) #next()实际调用了__next__()方法,此后不再多说
1
>>> next(e)
2
既然提到了for ... in ...
语句,我们再来简单讲下其工作原理吧,或许能帮助理解以上所讲的内容。
>>> x = [1, 2, 3]
>>> for i in x:
... ...
我们对一个iterable
用for ... in ...
进行迭代时,实际是先通过调用iter()
方法得到一个iterator
,假设叫做X。然后循环地调用X的next()
方法取得每一次的值,直到iterator为空,返回的StopIteration
作为循环结束的标志。for ... in ...
会自动处理StopIteration
异常,从而避免了抛出异常而使程序中断。如图所示
磨刀不误砍柴工,有了前面的知识,我们再来理解 generator
与 yield
将会事半功倍。
2. 生成器
生成器 是 这样一个函数,它记住上一次返回时在函数体中的位置。
对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。
生成器不仅 “记住” 了它的数据状态;生成器还 “记住” 了它的流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。
生成器的特点:
- 1. 生成器是一个函数,而且函数的参数都会保留。
- 2. 迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的
- 3. 节约内存
- 一个生成器函数的定义很像一个普通的函数,除了当它要生成一个值的时候,使用 yield 关键字而不是 return。如果一个 def 的主体包含 yield,这个函数会自动变成一个生成器(即使它包含一个 return)。创建一个生成器就这么简单。。。
生成器 比 迭代器 更加强大也更加复杂,需要花点功夫好好理解贯通。先理清几个概念:
generator: A function which returns a generator iterator. It looks like a normal function except that it contains yield expressions for producing a series of values usable in a for-loop or that can be retrieved one at a time with the next() function. generator iterator: An object created by a generator funcion. generator expression: An expression that returns an iterator.
以上的定义均来自 python官方文档。可见,我们常说的生成器
,就是带有 yield
的函数,而 generator iterator
则是generator function
的返回值,即一个generator
对象,而形如(elem for elem in [1, 2, 3])
的表达式,称为generator expression
,实际使用与generator
无异。
生成器 也是 迭代器,但是你只能对它们进行一次迭代,原因在于它们并没有将所有数据存储在内存中,而是即时生成这些数据:
>>> mygenerator = (x*x for x in range(3))
>>> for i in mygenerator:
... print(i)
0
1
4
这一段代码和上面 迭代 那段很相似,唯一不同的地方是使用了()
代替 []
。但是,这样的后果是你无法对 mygenerator
进行第二次 for i in mygenerator
,因为生成器只能被使用一次:它首先计算出结果0,然后忘记它再计算出1,最后是4,一个接一个。
>>> a = (elem for elem in [1, 2, 3])
>>> a
<generator object <genexpr> at 0x7f0d23888048>
>>> def fib():
... a, b = 0, 1
... while True:
... yield b
... a, b = b, a + b
...
>>> fib
<function fib at 0x7f0d238796a8>
>>> b = fib()
<generator object fib at 0x7f0d20bbfea0>
其实说白了,generator
就是 iterator
的一种,以更优雅的方式实现的 iterator
。
官方的说法是:Python’s generators provide a convenient way to implement the iterator protocol.
你完全可以像使用 iterator
一样使用 generator
,但是请记住他们两个的定义不一样:
- 定义一个
iterator
,你需要分别实现__iter__()
方法和__next__()
方法, - 但
generator
只需要一个小小的yield
( 好吧,generator expression
的使用比较简单,就不展开讲了。)
3. yield 关键字
Python 中 生成器 是使用 yield 关键字 来实现的。
- 1. yield 是一个用法跟 return 很相似的关键字,但是使用yield的函数返回的是一个 生成器。
- 2. yield 可以暂停一个函数并返回中间结果。使用 yield 的函数 将 保存执行环境,即函数的参数都会保留,并且在必要时恢复。到下一次的调用时,所有参数都恢复,所使用的参数都是第一次所保留下的参数和环境,然后从先前暂停的地方开始执行,直到遇到下一个 yield 再次 暂停。
看一段代码:
def test_func():for i in range(5):yield i #print(i + 100)t = test_func()
for i in t:print(i)pass
可以 单步调试 上面这个代码,就可以 验证 上面 两个 特点。
前文讲到 iterator
通过 __next__()
方法实现了每次调用,返回一个单一值的功能。而 yield
就是实现 generator
的 __next__()
方法的关键!先来看一个最简单的例子:
>>> def g():
... print("1 is")
... yield 1
... print("2 is")
... yield 2
... print("3 is")
... yield 3
...
>>> z = g()
>>> z
<generator object g at 0x7f0d2387c8b8>
>>> next(z)
1 is
1
>>> next(z)
2 is
2
>>> next(z)
3 is
3
>>> next(z)
Traceback (most recent call last):File "<stdin>", line 1, in <module>
StopIteration
解释:
- 第一次调用
next()
方法时,函数似乎执行到yield 1
,就暂停了。
(执行next方法,使程序运行到 yield 处暂停并返回1,然后交出控制权,然后 next 接收控制权,并获得 yield 的返回值 1) - 第二次调用
next()
方法时,函数从yield 1
之后开始执行的,并再次暂停。 - 第三次调用
next()
方法时,从第二次暂停的地方开始执行。 - 第四次调用
next()
方法时,抛出StopIteration
异常。
事实上,generator
确实在遇到 yield
之后暂停了,确切点说,是先返回了 yield
表达式的值,再暂停的。当再次调用 next()
时,从先前暂停的地方开始执行,直到遇到下一个 yield
。这与上文介绍的对iterator
调用next()
方法,执行原理一般无二。
有些教程里说 generator
保存的是算法,而我觉得用 中断服务子程序
来描述 generator
或许能更好理解,这样你就能将 yield
理解成一个中断服务子程序的 断点
,没错,是中断服务子程序的断点。我们每次对一个 generator
对象调用 next()
时,函数内部代码执行到 "断点" yield
,然后返回这一部分的结果,并保存上下文环境,"中断" 返回。
怎么样,是不是瞬间就明白了yield
的用法
我们再来看另一段代码。
>>> def gen():
... while True:
... s = yield
... print(s)
...
>>> g = gen()
>>> g.send("kissg")
Traceback (most recent call last):File "<stdin>", line 1, in <module>
TypeError: can't send non-None value to a just-started generator
>>> next(g)
>>> g.send("kissg")
kissg
我正是看到这个形式的 generator
,懵了,才想要深入学习 generator
与 yield
的。结合以上的知识,我再告诉你,generator
其实有 第 2 种调用方法(恢复执行),即通过 send(value)
方法将 value
作为 yield
表达式的当前值,你可以用该值再对其他变量进行赋值,这一段代码就很好理解了。当我们调用send(value)
方法时,generator
正由于yield
的缘故被暂停了。此时,send(value)
方法传入的值作为 yield
表达式的值,函数中又将该值赋给了变量 s
,然后 print 函数打印 s
,循环再遇到yield
,暂停返回。
调用 send(value)
时要注意,要确保 generator
是在 yield
处被暂停了,如此才能向 yield
表达式传值,否则将会报错(如上所示),可通过 next()
方法或 send(None)
使 generator
执行到 yield
。
再来看一段 yield
更复杂的用法,或许能加深你对 generator
的 next()
与 send(value)
的理解。
def echo(value=None):while 1:value = (yield value)print("The value is", value)if value:value += 1print('add +1 value', value)# 调用send(value)时要注意,要确保generator是在yield处被暂停了,
# 如此才能向yield表达式传值,否则将会报错
# 可通过next()方法或send(None)使generator执行到yield。
# 生成器(generator) 有两种方法 恢复执行:1. send() 方法。2. next() 方法g = echo(1) # 返回一个 生成器
print(next(g)) # 通过 next() 方法 使 生成器 执行到 yield 处暂停
g.send(2) # send(value)方法传入的值作为yield表达式的值
g.send(5)
next(g)
next(g)
next(g)
"""
执行结果:
1
The value is 2
add +1 value 3
The value is 5
add +1 value 6
The value is None
The value is None
The value is None
"""
上述代码既有 yield value 的形式,又有 value = yield 形式,看起来有点复杂。但以 yield 分离代码进行解读,就不太难了。
- 第一次调用 next()方法,执行到 yield value表达式,保存上下文环境暂停返回 1。
- 第二次调用 send(value)方法,从 value = yield 开始,打印,再次遇到 yield value 暂停返回。
- 后续的调用 send(value) 或 next()都是如此。
但是,这里就引出了另一个问题,yield 作为一个暂停恢复的点,代码从 yield 处恢复,又在下一个 yield 处暂停。可见,在一次 next()(非首次) 或 send(value)调用过程中,实际上存在 2 个 yield
- 一个作为恢复点的 yield
- 一个作为暂停点的yield
因此,也就有 2 个 yield 表达式。send(value)方法是将值传给恢复点yield。调用next()表达式的值时,其恢复点yield的值总是为None,而将暂停点的yield表达式的值返回。为方便记忆,你可以将此处的恢复点记作当前的(current),而将暂停点记作下一次的(next),这样就与next()方法匹配起来啦。
generator
还实现了另外两个方法throw(type[, value[, traceback]])
与close()
。前者用于抛出异常,后者用于关闭generator
.不过这2个方法似乎很少被直接用到,本文就不再多说了,有兴趣的同学请看这里。
示 例 解 释
>>> def createGenerator():
... mylist = range(3)
... for i in mylist:
... yield i*i
...
>>> mygenerator = createGenerator() # create a generator
>>> print(mygenerator) # mygenerator is an object!
<generator object createGenerator at 0xb7555c34>
>>> for i in mygenerator:
... print(i)
0
1
4
这是一个没有什么用的例子,但是用来让你了解当你知道你的函数会返回一个只会被遍历1次的巨大数据集合该怎么做的时候十分方便。为了掌握yield
,你必须了解当你调用这个函数的时候,你在函数体中写的代码并没有被执行,而是只返回了一个生成器对象,这个需要特别注意。然后,你的代码将会在每次for
使用这个生成器的时候被执行。最后,最困难的部分:
for
第一次调用通过你函数创建的生成器对象的时候,它将会从你函数的开头执行代码,一直到到达yield
,然后它将会返回循环中的第一个值。然后,其他每次调用都会再一次执行你在函数中写的那段循环,并返回下一个值,直到没有值可以返回。
生成器在函数执行了却没有到达yield
的时候将被认为是空的,原因在于循环到达了终点,或者不再满足if/else
条件。
>>> class Bank(): # let's create a bank, building ATMs
... crisis = False
... def create_atm(self):
... while not self.crisis:
... yield "$100"
>>> hsbc = Bank() # when everything's ok the ATM gives you as much as you want
>>> corner_street_atm = hsbc.create_atm()
>>> print(corner_street_atm.next())
$100
>>> print(corner_street_atm.next())
$100
>>> print([corner_street_atm.next() for cash in range(5)])
['$100', '$100', '$100', '$100', '$100']
>>> hsbc.crisis = True # crisis is coming, no more money!
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> wall_street_atm = hsbc.create_atm() # it's even true for new ATMs
>>> print(wall_street_atm.next())
<type 'exceptions.StopIteration'>
>>> hsbc.crisis = False # trouble is, even post-crisis the ATM remains empty
>>> print(corner_street_atm.next())
<type 'exceptions.StopIteration'>
>>> brand_new_atm = hsbc.create_atm() # build a new one to get back in business
>>> for cash in brand_new_atm:
... print cash
$100
$100
$100
$100
$100
$100
$100
$100
$100
...
首先看生成器的next
方法,它用来执行代码并从生成器中获取下一个元素(在Python 3.x中生成器已经没有next方法,而是使用next(iterator)代替)。在crisis
未被置为True
的时候,create_atm
函数中的while
循环可以看做是无尽的,当crisis
为True
的时候,跳出了while
循环,所有迭代器将会到达函数尾部,此时再次访问next
将会抛出StopIteration
异常,而此时就算将crisis
设置为False
,这些生成器仍然处在函数尾部,访问会继续抛出StopIteration
异常。
将以上例子用来控制访问资源等用途的时候十分有用。
示例解析:
#generation.py
def gen():for x in xrange(4):tmp = yield xif tmp == "hello":print "world"else:print "12345abcd", str(tmp)>>>from generation import gen
>>>c=gen()
>>>c.next()
0
>>>c.next()
12345abcd None
1
>>>c.send("python")
12345abcd python2
执行到 yield 时,gen 函数暂时停止并保存,返回x的值,同时tmp接收send的值(ps:yield x 相当于 return x ,所以第一次c.next()结果是0。第二次c.next()时,继续在原来暂停的地方执行,因为没有send 值,所以tmp 为 None。c.next()等价c.send(None))。下次c.send(“python”),send发送过来的值,c.next()等价c.send(None)
了解了next()如何让包含yield的函数执行后,我们再来看另外一个非常重要的函数send(msg)。其实next()和send()在一定意义上作用是相似的,区别是send()可以传递yield表达式的值进去,而next()不能传递特定的值,只能传递None进去。因此,我们可以看做c.next() 和 c.send(None) 作用是一样的。
需要提醒的是,第一次调用时,请使用next()语句或是send(None),不能使用send发送一个非None的值,否则会出错的,因为没有Python yield语句来接收这个值。
示例解析:
def gen():for x in range(4):tmp = yield xif tmp == 'hello':print('world')else:print(str(tmp))
只要函数中包含yield关键字,该函数调用就是生成器对象。
import typesdef gen():for x in range(4):tmp = yield xif tmp == 'hello':print('world')else:print(str(tmp))g = gen()
print(g) # <generator object gen at 0x02801760>
print(isinstance(g, types.GeneratorType)) # True
我们可以看到,gen() 并不是函数调用,而是产生生成器对象。
生成器对象支持几个方法,如 gen.next() ,gen.send() ,gen.throw() 等。
print(g.next()) # 0
调用生成器的 next 方法,将运行到 yield 位置,此时暂停执行环境,并返回yield后的值。所以打印出的是0,暂停执行环境。
print(g.next()) # None 1
再调用 next 方法,你也许会好奇,为啥打印出两个值,不急,且听我慢慢道来:上一次调用 next,执行到 yield 0暂停,再次执行恢复环境,给tmp赋值(注意:这里的tmp的值并不是x的值,而是通过send方法接受的值),由于我们没有调用send方法,所以 tmp 的值为None,此时输出None,并执行到下一次yield x,所以又输出1。
到了这里,next方法我们都懂了,下面看看send方法。
print(g.send('hello')) # world 2
上一次执行到 yield 1后暂停,此时我们send('hello'),那么程序将收到‘hello',并给tmp赋值为’hello',此时tmp=='hello'为真,所以输出'world',并执行到下一次yield 2,所以又打印出2.(next()等价于send(None))
当循环结束,将抛出StopIteration停止生成器。
看下面代码:
def stop_immediately(name):if name == 'sky_crab':yield 'ok_ok'else:print('no_no')s = stop_immediately('sky')# s.next()
# 在 python 3.x中 generator(有yield关键字的函数则会被识别为generator函数)中的next变为__next__了
# next() 是 python 3.x 以前版本中的方法, 在 python3 中使用 __next__() 代替
s.__next__()
正如你所预料的,打印出’no_no',由于没有额外的 yield,所以将直接抛出StopIteration。
Traceback (most recent call last):
no_noFile "E:/taopiao/text_3.py", line 32, in <module>s.__next__()
StopIteration
看下面代码,理解throw方法,throw主要是向生成器发送异常。
def mygen():try:yield 'something'except ValueError:yield 'value error'finally:print('clean') # 一定会被执行gg = mygen()
# print(gg.next()) # something
# print(gg.throw(ValueError)) # value error cleanprint(gg.__next__()) # something
print(gg.throw(ValueError)) # value error clean
调用 gg.next 很明显此时输出 ‘something’ ,并在yield ‘something’暂停,此时向gg发送ValueError异常,恢复执行环境,except 将会捕捉,并输出信息。
理解了这些,我们就可以向协同程序发起攻击了,所谓协同程序也就是是可以挂起,恢复,有多个进入点。其实说白了,也就是说多个函数可以同时进行,可以相互之间发送消息等。
这里有必要说一下multitask模块 ( 不是标准库模块,https://www.cnblogs.com/djw316/p/11734210.html) 看一段 multitask 使用的简单代码:
def tt():for x in range(4):print('tt' + str(x))yielddef gg():for x in range(4):print('xx' + str(x))yieldt = multitask.TaskManager()
t.add(tt())
t.add(gg())
t.run()
结果:
tt0
xx0
tt1
xx1
tt2
xx2
tt3
xx3
下载地址:https://pypi.org/search/?q=multitask,pypi 地址:https://pypi.org/project/python-multitasking/
安装:pip install python-multitasking 。使用示例:
import multitasking
import time
import random
import requests
import signal
import urllib3urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)# kill all tasks on ctrl-c
signal.signal(signal.SIGINT, multitasking.killall)# or, wait for task to finish on ctrl-c:
# signal.signal(signal.SIGINT, multitasking.wait_for_tasks)@multitasking.task # <== this is all it takes :-)
def hello(index):global global_list_flagurl = 'https://www.baidu.com/'print(f'{index} : {url}')req = requests.get(url, verify=False)print(f'{index} : {req.status_code}')global_list_flag[index-1] = 1if __name__ == "__main__":count = 10global_list_flag = [0 for _ in range(count)]for i in range(0, count):hello(i + 1)# https://www.yuanrenxue.com/python/python-asyncio-demo.htmlmultitasking.wait_for_tasks()
如果不是使用生成器,那么要实现上面现象,即函数交错输出,那么只能使用线程了,所以生成器给我们提供了更广阔的前景。
如果仅仅是实现上面的效果,其实很简单,我们可以自己写一个。主要思路就是将生成器对象放入队列,执行send(None)后,如果没有抛出StopIteration,将该生成器对象再加入队列。
# python 2.X 叫 Queue
# python 3.X 叫 queue
import queue
import multitaskdef tt():for x in range(4):print('tt' + str(x))yielddef gg():for x in range(4):print('xx' + str(x))yieldclass Task(object):def __init__(self):self._queue = queue.Queue()def add(self, gen):self._queue.put(gen)def run(self):while not self._queue.empty():for i in range(self._queue.qsize()):try:gen = self._queue.get()gen.send(None)except StopIteration:passelse:self._queue.put(gen)t = Task()
t.add(tt())
t.add(gg())
t.run()
当然,multitask 实现的肯定不止这个功能,有兴趣的童鞋可以看下源码,还是比较简单易懂的。
有这么一道题目,模拟多线程交替输出:
def thread1():for x in range(4):yield xdef thread2():for x in range(4, 8):yield xthreads = []
threads.append(thread1())
threads.append(thread2())def run(threads): # 写这个函数,模拟线程并发passrun(threads)
如果上面 class Task 看懂了,那么这题很简单,其实就是考你用yield模拟线程调度,解决如下:
def thread1():for x in range(4):yield xdef thread2():for x in range(4, 8):yield xtd_list = list()
td_list.append(thread1())
td_list.append(thread2())def run(thread_list):for td in thread_list:try:print(next(td))except StopIteration:passelse:thread_list.append(td)run(td_list)
3.itertools
itertools
模块包含了许多用来操作可迭代对象的函数。
想复制一个生成器?想连接两个生成器?想把多个值组合到一个嵌套列表里面?使用 map/zip
而不用重新创建一个列表?那么就:import itertools
吧。
让我们来看看四匹马赛跑可能的到达结果:
>>> horses = [1, 2, 3, 4]
>>> races = itertools.permutations(horses)
>>> print(races)
<itertools.permutations object at 0xb754f1dc>
>>> print(list(itertools.permutations(horses)))
[(1, 2, 3, 4),(1, 2, 4, 3),(1, 3, 2, 4),(1, 3, 4, 2),(1, 4, 2, 3),(1, 4, 3, 2),(2, 1, 3, 4),(2, 1, 4, 3),(2, 3, 1, 4),(2, 3, 4, 1),(2, 4, 1, 3),(2, 4, 3, 1),(3, 1, 2, 4),(3, 1, 4, 2),(3, 2, 1, 4),(3, 2, 4, 1),(3, 4, 1, 2),(3, 4, 2, 1),(4, 1, 2, 3),(4, 1, 3, 2),(4, 2, 1, 3),(4, 2, 3, 1),(4, 3, 1, 2),(4, 3, 2, 1)]
迭代的内部机理:
迭代是一个依赖于可迭代对象(需要实现__iter__()方法)和迭代器(需要实现__next__()方法)的过程。
可迭代对象是任意你可以从中得到一个迭代器的对象。
迭代器是让你可以对可迭代对象进行迭代的对象。
4. 总 结
-
可迭代对象(Iterable)是实现了
__iter__()
方法的对象,通过调用iter()
方法可以获得一个迭代器(Iterator)。 -
迭代器(Iterator)是实现了
__iter__()
和__next__()
的对象。 -
for ... in ...
的迭代,实际是将可迭代对象转换成迭代器,再重复调用next()
方法实现的。 -
生成器(generator)是一个特殊的迭代器,它的实现更简单优雅
-
yield
是生成器实现__next__()
方法的关键。它作为生成器执行的暂停恢复点,可以对yield
表达式进行赋值,也可以将yield
表达式的值返回。
yield
语句将你的函数转化成一个能够生成一种能够包装你原函数体的名叫生成器 的特殊对象的工厂。
当生成器被迭代时,它将会从起始位置开始执行函数一直到达下一个yield
,然后挂起执行,计算返回传递给yield
的值,它将会在每次迭代的时候重复这个过程直到函数执行到达函数的尾部,举例来说:
def simple_generator():yield 'one'yield 'two'yield 'three'
for i in simple_generator():print i输出结果为:
one
two
three
这种效果的产生是由于在循环中使用了可以产生序列的生成器,生成器在每次循环时执行代码到下一个yield
,并计算返回结果,这样生成器即时生成了一个列表,这对于特别是大型计算来说内存节省十分有效。
假设你想实现自己的可以产生一个可迭代一定范围数的range
函数(特指Python 2.x中的range
),你可以这样做和使用:
def myRangeNaive(i):n = 0range = []while n < i:range.append(n)n = n + 1return range
for i in myRangeNaive(10):print i
但是这样并不高效,原因1:你创建了一个你只会使用一次的列表;原因2:这段代码实际上循环了两次。
由于Guido和他的团队很慷慨地开发了生成器因此我们可以这样做:
def myRangeSmart(i):n = 0while n < i:yield nn = n + 1return
for i in myRangeSmart(10):print i
现在,每次对生成器迭代将会调用next()
来执行函数体直到到达yield
语句,然后停止执行,并计算返回结果,或者是到达函数体尾部。在这种情况下,第一次的调用next()
将会执行到yield n
并返回n
,下一次的next()
将会执行自增操作,然后回到while
的判断,如果满足条件,则再一次停止并返回n
,它将会以这种方式执行一直到不满足while
条件,使得生成器到达函数体尾部。
5. 提高你的 Python: 解释 yield 和 Generators(生成器)
来源: https://www.oschina.net/translate/improve-your-python-yield-and-generators-explained
我们调用一个普通的Python函数时,一般是从函数的第一行代码开始执行,结束于return语句、异常或者函数结束(可以看作隐式的返回None)。一旦函数将控制权交还给调用者,就意味着全部结束。函数中做的所有工作以及保存在局部变量中的数据都将丢失。再次调用这个函数时,一切都将从头创建。
对于在计算机编程中所讨论的函数,这是很标准的流程。这样的函数只能返回一个值,不过,有时可以创建能产生一个序列的函数还是有帮助的。要做到这一点,这种函数需要能够“保存自己的工作”。
我说过,能够“产生一个序列”是因为我们的函数并没有像通常意义那样返回。return隐含的意思是函数正将执行代码的控制权返回给函数被调用的地方。而"yield"的隐含意思是控制权的转移是临时和自愿的,我们的函数将来还会收回控制权。
在Python中,拥有这种能力的“函数”被称为生成器,它非常的有用。生成器(以及yield语句)最初的引入是为了让程序员可以更简单的编写用来产生值的序列的代码。 以前,要实现类似随机数生成器的东西,需要实现一个类或者一个模块,在生成数据的同时保持对每次调用之间状态的跟踪。引入生成器之后,这变得非常简单。
为了更好的理解生成器所解决的问题,让我们来看一个例子。在了解这个例子的过程中,请始终记住我们需要解决的问题:生成值的序列。
生成器。一个生成器会 “生成” 值。创建一个生成器几乎和生成器函数的原理一样简单。
一个生成器函数的定义很像一个普通的函数,除了当它要生成一个值的时候,使用yield关键字而不是return。如果一个def的主体包含yield,这个函数会自动变成一个生成器(即使它包含一个return)。除了以上内容,创建一个生成器没有什么多余步骤了。
生成器函数返回生成器的迭代器。这可能是你最后一次见到“生成器的迭代器”这个术语了, 因为它们通常就被称作“生成器”。要注意的是生成器就是一类特殊的迭代器。作为一个迭代器,生成器必须要定义一些方法(method),其中一个就是__next__()。如同迭代器一样,我们可以使用next()函数来获取下一个值。
为了从生成器获取下一个值,我们使用next()函数,就像对付迭代器一样。( next()会操心如何调用生成器的__next__()方法 )。
既然生成器是一个迭代器,它可以被用在for循环中。
每当生成器被调用的时候,它会返回一个值给调用者。其实 yield 就是专门给生成器使用的 return (加上点小魔法)。
下面是一个简单的生成器函数:
>>> def simple_generator_function():
>>> yield 1
>>> yield 2
>>> yield 3
这里有两个简单的方法来使用它:
>>> for value in simple_generator_function():
>>> print(value)
1
2
3
>>> our_generator = simple_generator_function()
>>> next(our_generator)
1
>>> next(our_generator)
2
>>> next(our_generator)
3
那么神奇的部分在哪里?我很高兴你问了这个问题!当一个生成器函数调用yield,生成器函数的“状态”会被冻结,所有的变量的值会被保留下来,下一行要执行的代码的位置也会被记录,直到再次调用next()。一旦next()再次被调用,生成器函数会从它上次离开的地方开始。如果永远不调用next(),yield保存的状态就被无视了。
我们来重写get_primes()函数,这次我们把它写作一个生成器。注意我们不再需要magical_infinite_range函数了。使用一个简单的while循环,我们创造了自己的无穷串列。
def get_primes(number):while True:if is_prime(number):yield numbernumber += 1
如果生成器函数调用了return,或者执行到函数的末尾,会出现一个StopIteration异常。 这会通知next()的调用者这个生成器没有下一个值了(这就是普通迭代器的行为)。这也是这个while循环在我们的get_primes()函数出现的原因。如果没有这个while,当我们第二次调用next()的时候,生成器函数会执行到函数末尾,触发StopIteration异常。一旦生成器的值用完了,再调用next()就会出现错误,所以你只能将每个生成器的使用一次。下面的代码是错误的:
>>> our_generator = simple_generator_function()
>>> for value in our_generator:
>>> print(value)>>> # 我们的生成器没有下一个值了...
>>> print(next(our_generator))
Traceback (most recent call last):File "<ipython-input-13-7e48a609051a>", line 1, in <module>next(our_generator)
StopIteration>>> # 然而,我们总可以再创建一个生成器
>>> # 只需再次调用生成器函数即可>>> new_generator = simple_generator_function()
>>> print(next(new_generator)) # 工作正常
1
因此,这个while循环是用来确保生成器函数永远也不会执行到函数末尾的。只要调用next()这个生成器就会生成一个值。这是一个处理无穷序列的常见方法(这类生成器也是很常见的)。
5.1 执行流程
让我们回到调用 get_primes 的地方:solve_number_10。
def solve_number_10():# She *is* working on Project Euler #10, I knew it!total = 2for next_prime in get_primes(3):if next_prime < 2000000:total += next_primeelse:print(total)return
我们来看一下solve_number_10的for循环中对get_primes的调用,观察一下前几个元素是如何创建的有助于我们的理解。当for循环从get_primes请求第一个值时,我们进入get_primes,这时与进入普通函数没有区别。
- 进入第三行的while循环
- 停在if条件判断(3是素数)
- 通过yield将3和执行控制权返回给solve_number_10
接下来,回到insolve_number_10:
- for循环得到返回值3
- for循环将其赋给next_prime
- total加上next_prime
- for循环从get_primes请求下一个值
这次,进入get_primes时并没有从开头执行,我们从第5行继续执行,也就是上次离开的地方。
def get_primes(number):while True:if is_prime(number):yield numbernumber += 1 # <<<<<<<<<<
最关键的是,number还保持我们上次调用yield时的值(例如3)。记住,yield会将值传给next()的调用方,同时还会保存生成器函数的“状态”。接下来,number加到4,回到while循环的开始处,然后继续增加直到得到下一个素数(5)。我们再一次把number的值通过yield返回给solve_number_10的for循环。这个周期会一直执行,直到for循环结束(得到的素数大于2,000,000)。
更给力点
在PEP 342中加入了将值传给生成器的支持。PEP 342加入了新的特性,能让生成器在单一语句中实现,生成一个值(像从前一样),接受一个值,或同时生成一个值并接受一个值。
我们用前面那个关于素数的函数来展示如何将一个值传给生成器。这一次,我们不再简单地生成比某个数大的素数,而是找出比某个数的等比级数大的最小素数(例如10, 我们要生成比10,100,1000,10000 ... 大的最小素数)。我们从get_primes开始:
def print_successive_primes(iterations, base=10):# 像普通函数一样,生成器函数可以接受一个参数prime_generator = get_primes(base)# 这里以后要加上点什么for power in range(iterations):# 这里以后要加上点什么def get_primes(number):while True:if is_prime(number):# 这里怎么写?
get_primes的后几行需要着重解释。yield关键字返回number的值,而像 other = yield foo 这样的语句的意思是,"返回foo的值,这个值返回给调用者的同时,将other的值也设置为那个值"。你可以通过send方法来将一个值”发送“给生成器。
def get_primes(number):while True:if is_prime(number):number = yield numbernumber += 1
通过这种方式,我们可以在每次执行yield的时候为number设置不同的值。现在我们可以补齐print_successive_primes中缺少的那部分代码:
def print_successive_primes(iterations, base=10):prime_generator = get_primes(base)prime_generator.send(None)for power in range(iterations):print(prime_generator.send(base ** power))
这里有两点需要注意:首先,我们打印的是generator.send的结果,这是没问题的,因为send在发送数据给生成器的同时还返回生成器通过yield生成的值(就如同生成器中yield语句做的那样)。
第二点,看一下prime_generator.send(None)这一行,当你用send来“启动”一个生成器时(就是从生成器函数的第一行代码执行到第一个yield语句的位置),你必须发送None。这不难理解,根据刚才的描述,生成器还没有走到第一个yield语句,如果我们发生一个真实的值,这时是没有人去“接收”它的。一旦生成器启动了,我们就可以像上面那样发送数据了。
综述
在本系列文章的后半部分,我们将讨论一些yield的高级用法及其效果。yield已经成为Python最强大的关键字之一。现在我们已经对yield是如何工作的有了充分的理解,我们已经有了必要的知识,可以去了解yield的一些更“费解”的应用场景。
不管你信不信,我们其实只是揭开了yield强大能力的一角。例如,send确实如前面说的那样工作,但是在像我们的例子这样,只是生成简单的序列的场景下,send几乎从来不会被用到。下面我贴一段代码,展示send通常的使用方式。对于这段代码如何工作以及为何可以这样工作,在此我并不打算多说,它将作为第二部分很不错的热身。
import randomdef get_data():"""返回0到9之间的3个随机数"""return random.sample(range(10), 3)def consume():"""显示每次传入的整数列表的动态平均值"""running_sum = 0data_items_seen = 0while True:data = yielddata_items_seen += len(data)running_sum += sum(data)print('The running average is {}'.format(running_sum / float(data_items_seen)))def produce(consumer):"""产生序列集合,传递给消费函数(consumer)"""while True:data = get_data()print('Produced {}'.format(data))consumer.send(data)yieldif __name__ == '__main__':consumer = consume()consumer.send(None)producer = produce(consumer)for _ in range(10):print('Producing...')next(producer)