迭代器(Iterator)和可迭代对象(Iterable)每每是绑定的。可迭代对象就是咱们平时常常用的list
,string
, tuple
这种。事实上迭代器的概念会比可迭代对象宽泛不少,一会举几个例子就能明白。python
在使用list
这种数据类型的时候,咱们常常会使用下面这种迭代方式:express
# eg 1 mylist = [1,2,3,4] for x in mylist: print(x) >>>1 >>>2 >>>3 >>>4
有时候会很奇怪for循环为何能够这么用,列表的索引居然会自动日后一个一个走,走到结尾了,还会自动停下来,不会有list out of range
的报错。神奇。其实for循环作的事情不止这么简单。要说明这个问题,得先说迭代器具体怎么作,是什么。函数
要建立一个迭代器,就必需要实现两个方法,分别是__iter__()
和__next__()
,以及实现异常机制StopIteration
。请看下面的例子:code
# eg 2 class PowTwo: """Class to implement an iterator of powers of two""" def __init__(self, max = 0): self.max = max def __iter__(self): self.n = 0 return self def __next__(self): if self.n <= self.max: result = 2 ** self.n self.n += 1 return result else: raise StopIteration
能够看到,迭代器是经过写一个类来定义的,类里面实现了刚刚所说的__iter__()
和__next__()
方法。这个迭代器是用来产生一系列2的指数次方的数字的,具体用法看下面:对象
a = PowTwo(4) i = iter(a) # attention please next(i) >>>1 next(i) >>>2 next(i) >>>4 next(i) >>>8 next(i) >>>16 next(i) >>>Traceback (most recent call last): ... StopIteration
仔细看哦,第一行代码用4建立了一个实例a
,设置了这个迭代器的迭代上限,而后并非直接用这个实例就能够了,还得调用iter()
函数去把a
完全进化成一个Iterator
,进而有了接下来next()
函数的闪亮登场。其实我也蛮奇怪,直接用这个类很差么,好比下面的代码:索引
a = PowTwo(4) a.__iter__() a.__next__() >>>1 a.__next__() >>>2 a.__next__() >>>4 a.__next__() >>>8 a.__next__() >>>16 next(i) >>>Traceback (most recent call last): ... StopIteration
彻底没问题,可是你本身比较一下两者的代码,哪一个美一点毋庸置疑。。。再说了,跟装饰器同样,一切为了简洁优美而生嘛。element
OK,能够回到最初的起点——for循环了。示例1中的代码,for循环到底作了什么呢,答案是,for循环实际上是先把mylist
变成迭代器,而后用while循环去迭代:generator
iter_obj = iter(mylist) while True: try: element = next(iter_obj) except StopIteration: break
这样一路解释过来,应该就不难理解迭代器了。总结一下就是:string
iter()
函数去初始化它,而后疯狂next()
便可;__iter__()
和__next__()
方法,以及异常机制StopIteration
,而后操做同1;StopIteration
便可。 这玩意儿就比迭代器复杂点了,因此还得分几个小点,逐个击破。it
生成器也是Python中面向一系列须要迭代的问题,经常使用的解决方案。既然有了迭代器,能够解决不少迭代的问题,那为啥子还要生成器勒?
主要的缘由是迭代器的开销太大了。对于一些小问题还好,大的问题须要迭代的元素很庞大的时候,迭代器就使不上劲儿了。并且,建立一个迭代器,说实话也还挺麻烦的,看看上面的小总结的第二点,你得实现这些方法和手动处理异常。
并且迭代器要写类,其实一个函数能够搞定的事情,何须那么复杂。正是应了这个景,生成器也就是在函数对象上搞事情的。
这个缘由点到即止,先把生成器讲清楚了,天然就通透了。先看一个小例子,写一个生成器函数(Generator Function):
# eg 1 def my_gen(): n = 1 print('This is printed first') yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n
上面就是一个简单的生成器函数,多了一种关键字yield
,细心看会发现这个函数居然没有return
!再细看代码,没错,yield
替代了return
。那怎么用呢,有两种用法以下:
# usage 1 a = my_gen() next(a) >>>This is printed first 1 next(a) >>>This is printed second 2 next(a) >>>This is printed at last 3 next(a) >>>Traceback (most recent call last): ... StopIteration
# usage 2 for item in my_gen(): print(item) >>> This is printed first 1 This is printed second 2 This is printed at last 3
对于用法1,把函数赋值给了a
,然乎疯狂next()
便可。你会发现,咱们并无像迭代器那样实现__iter__()
和__next__()
方法,以及异常机制StopIteration
,只是用了一个yield
关键字,这个生成器函数却达到了迭代器同样的效果。
对于用法2,更牛皮了,甚至不用赋值的操做,直接for这个生成器函数。。。
刚刚那个小函数,就是一个最普通的例子,那问题是若是有多个n想要玩,岂不是来多少手动写多少?那固然还有循环的玩法,带有循环机制的生成器。下面是一个逆序输出的小例子:
# eg 2 def rev_str(my_str): length = len(my_str) for i in range(length - 1,-1,-1): yield my_str[i] for char in rev_str("hello"): print(char) >>>o >>>l >>>l >>>e >>>h
没错,真无聊,犯得上逆序输出的程序还得上生成器么,犯不着,可是,只是想给出这个循环机制生成器的概念。若是你发现这个rev_str()
函数和普通的逆序函数彻底同样,只是return
换成了yield
,那就万事大吉,要理解的就是这个。就这样一记简单的替换操做,你就获得了一个生成器,是否是比迭代器省事儿多了。
不知道你有没有用过列表生成式,没用过也应该看到过,这相似于匿名函数,语法简洁,好比:
# eg 3 my_list = [1, 3, 6, 10] [x**2 for x in my_list] >>>[1, 9, 36, 100]
生成器表达式和这个几乎同样,不信你看:
# eg 4 my_list = [1, 3, 6, 10] a = (x**2 for x in my_list) next(a) >>>1 next(a) >>>9 next(a) >>>36 next(a) >>>100
把列表生成式的[]
直接改为()
,就获得了一个生成器。
回到最开始迭代器的那个类的例子,用生成器咋写呢?
def PowTwoGen(max = 0): n = 0 while n < max: yield 2 ** n n += 1
简洁吧,而后你就能够用这个生成器函数遨游了。
一样的一个须要迭代的功能,若是用普通函数写,一旦须要迭代的元素特别多,在使用的时候,普通函数须要等全部的元素计算出来了,而后把返回值给你。生成器就不是了,它一次计算出一个,用的时候就取一个,而且它还会记住位置,下次用就计算下一个,这样对空间的开销也是很小的。
看下面的函数:
def all_even(): n = 0 while True: yield n n += 2
写普通函数,你必然作不到写出一个能够无限操做的函数,生成器却能够。(迭代器也能够,就是麻烦点儿)
# 利用yield生成一个斐波那契数列的生成器 def fib(max): n,a,b=0,0,1 while n<max: yield b a,b=b,a+b n+=1 return 'done' # 要不要这句话都行 f=fib(6) next(f) # 疯狂next()它 >>> 1 1 2 3 5 8 Traceback (most recent call last): ... StopIteration: done