生成器函数在Python中与迭代器协议的概念联系在一起。包含yield语句的函数会被特地编译成生成器 !!!
当函数被调用时,他们返回一个生成器对象,这个对象支持迭代器接口。
不像一般的函数会生成值后退出,生成器函数在生成值后会自动挂起并暂停他们的执行和状态,他的本地变量将保存状态信息,这些信息在函数恢复时将再度有效
创建生成器方式有两种:
方法一:
1 | s = ( x for x in range ( 5 ) ) |
方法二:
1 2 3 | def foo(): print ( 'OK' ) yield 1 |
例子:
1 2 3 4 5 6 | def g(n): for i in range (n): yield i * * 2 for i in g( 5 ): print (i) |
要了解他的运行原理,我们来用next方法看看:
1 2 3 4 5 6 7 8 9 10 11 | t = g( 5 ) print (t.__next__()) # 0 print (t.__next__()) # 1 print (t.__next__()) # 4 print (t.__next__()) # 9 print (t.__next__()) # 16 print (t.__next__()) Traceback (most recent call last): File "<stdin>" , line 1 , in <module> StopIteration |
在运行完5次next之后,生成器抛出了一个StopIteration异常,迭代终止。
send(msg) 与 next()
了解了next()如何让包含yield的函数执行后,我们再来看另外一个非常重要的函数send(msg)。
其实next()和send()在一定意义上作用是相似的,区别是send()可以传递yield表达式的值进去,而next()不能传递特定的值,只能传递None进去。因此,我们可以看做
c.next() 和 c.send(None) 作用是一样的。
1 2 3 4 5 6 7 8 9 10 11 | def g(n): for i in range (n): ret = yield i * * 2 print (ret) t = g( 5 ) print (t.__next__()) print (t.send( 'Hello' )) # 0 Hello 1 |
需要注意的是,第一次调用时,请使用next()语句或是send(None),不能使用send发送一个非None的值,否则会出错的,因为没有yield语句来接收这个值。
send(msg) 和 next()是有返回值的,它们的返回值很特殊,返回的是yield表达式的参数 !
执行顺序是:遇到yield 先返回值,等下次再进入时再用 msg 进行赋值!!!
再来看一个yield的例子,用生成器生成一个Fibonacci数列:
1 2 3 4 5 6 7 8 9 10 | def fab( max ): a, b = 0 , 1 while a < max : yield a a, b = b, a + b for i in fab( 20 ): print (i) # 0 1 1 2 3 5 8 13 |
另一个 yield 的例子来源于文件读取。
1 2 3 4 5 6 7 8 9 | def read_file(fpath): BLOCK_SIZE = 1024 with open (fpath, 'rb' ) as f: while True : block = f.read(BLOCK_SIZE) if block: yield block else : return |
for循环可以用于Python中的任何类型,包括列表、元祖等等,实际上,for循环可用于任何“可迭代对象”!
迭代器是一个实现了迭代器协议的对象,Python中的迭代器协议就是有next方法的对象会前进到下一结果,而在一系列结果的末尾是,则会引发StopIteration。
任何这类的对象在Python中都可以用for循环或其他遍历工具迭代,迭代工具内部会在每次迭代时调用next方法,并且捕捉StopIteration异常来确定何时离开。
使用迭代器一个显而易见的好处就是:每次只从对象中读取一条数据,不会造成内存的过大开销。
注意:
判断迭代器的条件是:
有__iter__ 方法
有__next__ 方法
所有的生成器都是迭代器!
迭代器例子:
比如要逐行读取一个文件的内容,利用readlines()方法,我们可以这么写:
1 2 | for line in open ( "test.txt" ).readlines(): print (line) |
这样虽然可以工作,但不是最好的方法。因为他实际上是把文件一次加载到内存中,然后逐行打印。当文件很大时,这个方法的内存开销就很大了。
利用file的迭代器,我们可以这样写:
1 2 | for line in open ( "test.txt" ): print (line) |
这是最简单也是运行速度最快的写法,他并没显式的读取文件,而是利用迭代器每次读取下一行。
for 循环实质:
调用 __iter__ 方法将可迭代对象转换成迭代器
对迭代器对象不断调用 __next__ 方法
处理StopIteration 异常
判断条件:内部具有 __iter__ 方法
1 2 3 4 5 6 | from collections import Iterable,Iterator print ( isinstance ( range ( 1 ),Iterable)) print ( isinstance ( range ( 1 ),Iterator)) print ( isinstance ( list (),Iterable)) print ( isinstance ( list (),Iterator)) |
联系客服