Python Iterable Iterator Yield
可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list / tuple / dict / set / str /等(对于这类iterable的对象,因为你可以按照你的意愿进行重复的读取。但是你不得不预先存储所有的元素在内存中,那些对象里有很多元素时,并不是每一项都对你有用)
一类是generator,包括生成器和带yield的generator function。(生成器同样是可迭代对象,但是你只能读取一次,因为它并没有把所有值存放内存中,它动态的生成值)
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
可以使用isinstance()判断一个对象是否是Iterable对象:
>>> from collections import Iterable >>> isinstance('abc', Iterable) True >>> isinstance([1,2,3], Iterable) True >>> isinstance(100, Iterable) False >>> isinstance((x for x in range(10)), Iterable) True >>> isinstance((x for x in xrange(10)), Iterable) True >>> isinstance(set(),Iterable) True
而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。
可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。
可以使用isinstance()判断一个对象是否是Iterator对象:
>>> from collections import Iterator >>> isinstance((x for x in range(20)),Iterator) True >>> isinstance([1,2,3],Iterator) False >>> isinstance(set(),Iterator) False
生成器都是Iterator对象,但 list / dict / str虽然是Iterable,却不是Iterator
为什么list / dict / str 等数据类型不是Iterator ?
这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看作是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
小结
凡是可作用于for循环的对象都是Iterable(可迭代对象)类型;
凡是可以作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
集合数据类型如 list / dict / str / 等是Iterable可迭代对象但不是Iterator迭代器,不过可以通过iter()函数可以获得一个Iterator对象。
Python的for循环本质上就是通过不断调用next()函数实现的,例如:
for x in [1,2,3,4,5]: print x ix=iter([1,2,3,4,5]) while True: try: print ix.next() except StopIteration: print "Bottom of Iterator" break
Yield是关键字, 用起来像return,yield在告诉程序,要求函数返回一个生成器
def createGenerator() : mylist = range(3) for i in mylist : yield i*i mygenerator = createGenerator() # create a generator print (mygenerator) # mygenerator is an object! print getsizeof(mygenerator) <generator object createGenerator at 0x0000000009626D38> 72
一个计算斐波那契(Fibonacci)的例子
a)
def fab(max): n, a, b =0, 0, 1 while n < max: print b a, b =b, a +b n =n +1 fab(5)
b)a的写法会直接在 fab 函数中用 print 打印数字会导致该函数可复用性较差,因为 fab 函数返回 None,其他函数无法获得该函数生成的数列。要提高 fab 函数的可复用性,最好不要直接打印出数列,而是返回一个 List.
def fab(max): n, a, b =0, 0, 1 L =[] while n < max: L.append(b) a, b =b, a +b n =n +1 return L for n in fab(5): print n
c) b在运行中占用的内存会随着参数 max 的增大而增大,如果要控制内存占用,最好不要用 List来保存中间结果,而是通过 iterable 对象来迭代
def fab(max): n, a, b =0, 0, 1 L =[] for i in xrange(max): L.append(b) a, b =b, a +b return L for n in fab(5): print n
d)
class Fab(object): def __init__(self, max): self.max=max self.n, self.a, self.b =0, 0, 1 def __iter__(self): returnself def next(self): if self.n < self.max: r =self.b self.a, self.b =self.b, self.a +self.b self.n =self.n +1 return r raise StopIteration() f=fab(5) f.next() f.next() f.next() f.next() f.next()
e)d的代码远远没有a的fab 函数来得简洁。如果我们想要保持第一版 fab 函数的简洁性,同时又要获得 iterable 的效果,yield 就派上用场了
def fab(max): n, a, b =0, 0, 1 while n < max: yield b # print b a, b =b, a +b n =n +1 for n in fab(5): print n type(fab(5)) 1 1 2 3 5 generator
f)最后优化的版本在for循环的时候选择xrange并且结果保存在iterator里
def fab(max): n,a,b=0,0,1 for i in xrange(max): yield b a,b,=b,a+b for n in fab(5): print n
一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。
yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰
from inspect import isgeneratorfunction isgeneratorfunction(fab) True
要注意区分 fab 和 fab(5),fab 是一个 generator function,而 fab(5) 是调用 fab 返回的一个 generator,好比类的定义和类的实例的区别
在一个 generator function 中,如果没有 return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代
import types isinstance(fab,types.GeneratorType) False isinstance(fab(5),types.GeneratorType) True from collections import Iterable isinstance(fab,Iterable) False isinstance(fab(5),Iterable) True
另一个例子
另一个 yield 的例子来源于文件读取。如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取
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 rf=read_file("C:/****/cookies.txt") print rf.next() print rf.next() print rf.next()