那如今咱们就使用while循环实现了本来for循环作的事情,咱们是从谁那儿获取一个一个的值呀?是否是就是l_iter?好了,这个l_iter就是一个迭代器。
迭代器遵循 迭代器协议 :必须拥有__iter__方法和__next__方法。
还帐:next和iter方法
什么是迭代(iteration)?
若是给定一个list或tuple,咱们要想访问其中的某个元素,咱们能够经过下标来,若是咱们想要访问全部的元素,那咱们能够用for循环来遍历这个list或者tuple,而这种遍历咱们就叫作迭代。面试
可迭代(iterable)?编程
其实你已经知道,不是全部的数据类型都是可迭代的。那么可迭代的数据类型都有什么特色呢?app
print(dir([1,2])) print(dir((2,3))) print(dir({1:2})) print(dir({1,2}))
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__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'] ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index'] ['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values'] ['__and__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__iand__', '__init__', '__ior__', '__isub__', '__iter__', '__ixor__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__or__', '__rand__', '__reduce__', '__reduce_ex__', '__repr__', '__ror__', '__rsub__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__xor__', 'add', 'clear', 'copy', 'difference', 'difference_update', 'discard', 'intersection', 'intersection_update', 'isdisjoint', 'issubset', 'issuperset', 'pop', 'remove', 'symmetric_difference', 'symmetric_difference_update', 'union', 'update']
总结一下咱们如今所知道的:能够被for循环的都是可迭代的,要想可迭代,内部必须有一个__iter__方法。迭代器协议ssh
from collections import Iterable l = [1,2,3,4] t = (1,2,3,4) d = {1:2,3:4} s = {1,2,3,4} print(isinstance(l,Iterable)) print('__iter__' in dir([l])) print(isinstance(t,Iterable)) print('__iter__' in dir([t])) print(isinstance(d,Iterable)) print('__iter__' in dir([d])) print(isinstance(s,Iterable)) print('__iter__' in dir([s]))
接着分析,__iter__方法作了什么事情呢?ide
print([1,2].__iter__()) 结果 <list_iterator object at 0x1024784a8>
执行了list([1,2])的__iter__方法,咱们好像获得了一个list_iterator,如今咱们又获得了一个新名词——iterator,即迭代器。函数
咱们调用可迭代对象的__iter__方法以后,获得了一个新东西--迭代器。post
那迭代器是什么呢?请继续日后阅读吧。测试
既什么叫“可迭代”以后,又一个历史新难题,什么叫“迭代器”?spa
虽然咱们不知道什么叫迭代器,可是咱们如今已经有一个迭代器了,这个迭代器是一个列表的迭代器。3d
咱们来看看这个列表的迭代器比起列表来讲实现了哪些新方法,这样就能揭开迭代器的神秘面纱了吧?
''' dir([1,2].__iter__())是列表迭代器中实现的全部方法,dir([1,2])是列表中实现的全部方法,都是以列表的形式返回给咱们的,为了看的更清楚,咱们分别把他们转换成集合, 而后取差集。 ''' #print(dir([1,2].__iter__())) #print(dir([1,2])) print(set(dir([1,2].__iter__()))-set(dir([1,2]))) 结果: {'__length_hint__', '__next__', '__setstate__'}
咱们看到在列表迭代器中多了三个方法,那么这三个方法都分别作了什么事呢?
iter_l = [1,2,3,4,5,6].__iter__() #获取迭代器中元素的长度 print(iter_l.__length_hint__()) #根据索引值指定从哪里开始迭代 print('*',iter_l.__setstate__(4)) #一个一个的取值 print('**',iter_l.__next__()) print('***',iter_l.__next__())
这三个方法中,能让咱们一个一个取值的神奇方法是谁?
没错!就是__next__
在for循环中,就是在内部调用了__next__方法才能取到一个一个的值。
那接下来咱们就用迭代器的next方法来写一个不依赖for的遍历。
l = [1,2,3,4] l_iter = l.__iter__() item = l_iter.__next__() print(item) item = l_iter.__next__() print(item) item = l_iter.__next__() print(item) item = l_iter.__next__() print(item) item = l_iter.__next__() print(item)
这是一段会报错的代码,若是咱们一直取next取到迭代器里已经没有元素了,就会抛出一个异常StopIteration,告诉咱们,列表中已经没有有效的元素了。
这个时候,咱们就要使用异常处理机制来把这个异常处理掉。
l = [1,2,3,4] l_iter = l.__iter__() while True: try: item = l_iter.__next__() print(item) except StopIteration: break
那如今咱们就使用while循环实现了本来for循环作的事情,咱们是从谁那儿获取一个一个的值呀?是否是就是l_iter?好了,这个l_iter就是一个迭代器。
迭代器遵循 迭代器协议 :必须拥有__iter__方法和__next__方法。
还帐:next和iter方法
如此一来,关于迭代器和生成器的方法咱们就还清了两个,最后咱们来看看 range()是个啥。首先,它确定是一个可迭代的对象,可是它是不是一个迭代器?咱们来测试一下
print('__next__' in dir(range(12))) #查看'__next__'是否是在range()方法执行以后内部是否有__next__ print('__iter__' in dir(range(12))) #查看'__next__'是否是在range()方法执行以后内部是否有__next__ from collections import Iterator print(isinstance(range(100000000),Iterator)) #验证range执行以后获得的结果不是一个迭代器
为何要有for循环
基于上面讲的列表这一大堆遍历方式,聪明的你立马看除了端倪,因而你不知死活大声喊道,你这不逗我玩呢么,有了下标的访问方式,我能够这样遍历一个列表啊
l=[1,2,3] index=0 while index < len(l): print(l[index]) index+=1 #要毛线for循环,要毛线可迭代,要毛线迭代器
没错,序列类型字符串,列表,元组都有下标,你用上述的方式访问,perfect!可是你可曾想过非序列类型像字典,集合,文件对象的感觉,因此嘛,年轻人,for循环就是基于迭代器协议提供了一个统一的能够遍历全部对象的方法,即在遍历以前,先调用对象的__iter__方法将其转换成一个迭代器,而后使用迭代器协议去实现循环访问,这样全部的对象就均可以经过for循环来遍历了,并且你看到的效果也确实如此,这就是无所不能的for循环,觉悟吧,年轻人
咱们知道的迭代器有两种:一种是调用方法直接返回的,一种是可迭代对象经过执行 iter方法获得的,迭代器有的好处是能够节省内存。
若是在某些状况下,咱们也须要节省内存,就只能本身写。咱们本身写的这个能实现迭代器功能的东西就叫生成器。
Python 中提供的 生成器:
1.生成器函数:常规函数定义,可是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每一个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行
2.生成器表达式:相似于列表推导,可是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
生成器Generator:
本质:迭代器 ( 因此自带了 __iter__方法和 __next__方法,不须要咱们去实现)
特色:惰性运算,开发者自定义
一个包含yield关键字的函数就是一个生成器函数。
yield和return同样能够从函数中返回值,可是yield又不一样于return,return的执行意味着程序的结束,只能返回一次,yield能够返回屡次。
调用生成器函数不会获得返回的具体的值,而是获得一个生成器对象。
每一次从这个可迭代对象获取值,就能推进函数的执行,获取新的返回值。直到函数执行结束(yield像是拥有可以让函数暂停的魔力)。
def my_range(): print('我是一个生成器函数') n = 0 while 1: yield n n += 1
生成器有什么好处呢?就是不会一会儿在内存中生成太多数据
接下来,咱们在这个函数的基础上来写一个咱们本身的range函数,实现开始和结束
def my_range2(start, stop): n = start while n < stop: yield n n += 1
再进一步,实现步长:
def my_range3(start, stop, step): n = start while n < stop: yield n n += step
生成器本质上就是个迭代器,咱们根据本身的想法创造的迭代器,它固然也支持for循环:
for i in my_range3(1, 10, 2): print(i)
import time def tail(filename): f = open(filename) f.seek(0, 2) #从文件末尾算起 while True: line = f.readline() # 读取文件中新的文本行 if not line: time.sleep(0.1) continue yield line tail_g = tail('tmp') for line in tail_g: print(line)
yield能够返回值,也能够接收值。
经过生成器的send方法能够给yield传值。
def eat(name): print('%s要开始吃了!' % name) while 1: food = yield print('{}在吃{}'.format(name, food)) a = eat('alex') a.__next__() # 初始化,让函数暂停在yield处 a.send('包子') # send两个做用:1.给yield传值 2.继续执行函数 a.send('饺子')
yield能够同时返回值和接收值。
def averager(): total = 0.0 count = 0 average = None while True: term = yield average total += term count += 1 average = total/count g_avg = averager() next(g_avg) print(g_avg.send(10)) print(g_avg.send(30)) print(g_avg.send(5))
def init(func): #在调用被装饰生成器函数的时候首先用next激活生成器 def inner(*args,**kwargs): g = func(*args,**kwargs) next(g) return g return inner @init def averager(): total = 0.0 count = 0 average = None while True: term = yield average total += term count += 1 average = total/count g_avg = averager() # next(g_avg) 在装饰器中执行了next方法 print(g_avg.send(10)) print(g_avg.send(30)) print(g_avg.send(5))
在一个生成器中引用另一个生成器。
def gen1(): for c in 'AB': yield c for i in range(3): yield i print(list(gen1())) def gen2(): yield from 'AB' yield from range(3) print(list(gen2())) yield from
#老男孩因为峰哥的强势加盟很快走上了上市之路,alex思来想去决定下几个鸡蛋来报答峰哥 egg_list=['鸡蛋%s' %i for i in range(10)] #列表解析 #峰哥瞅着alex下的一筐鸡蛋,捂住了鼻子,说了句:哥,你仍是给我只母鸡吧,我本身回家下 laomuji=('鸡蛋%s' %i for i in range(10))#生成器表达式 print(laomuji) print(next(laomuji)) #next本质就是调用__next__ print(laomuji.__next__()) print(next(laomuji))
总结:
1.把列表解析的[]换成()获得的就是生成器表达式
2.列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存
3.Python不但使用迭代器协议,让for循环变得更加通用。大部份内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,因此,咱们能够直接这样计算一系列值的和:
sum(x ** 2 for x in range(4))
sum([x ** 2 for x in range(4)])
def demo(): for i in range(4): yield i g=demo() g1=(i for i in g) g2=(i for i in g1) print(list(g1)) print(list(g2))
def add(a, b): return a + b def func(): for i in range(4): yield i g = func() for n in [1, 10]: g = (add(n, i) for i in g) print(list(g))