什么叫迭代编程
1234不能够for循环,是由于它不可迭代。那么若是“可迭代”,就应该能够被for循环了。ide
这个咱们知道呀,字符串、列表、元组、字典、集合均可以被for循环,说明他们都是可迭代的。函数
咱们怎么来证实这一点呢?测试
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(isinstance(t,Iterable)) print(isinstance(d,Iterable)) print(isinstance(s,Iterable))
结合咱们使用for循环取值的现象,再从字面上理解一下,其实迭代就是咱们刚刚说的,能够将某个数据集内的数据“一个挨着一个的取出来”,就叫作迭代。大数据
可迭代协议spa
能够被迭代要知足的要求就叫作可迭代协议。可迭代协议的定义很是简单,就是内部实现了__iter__方法。3d
迭代器协议code
''' 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__())
在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__方法。
咱们来看看range()是个啥。首先,它确定是一个可迭代的对象,可是它是不是一个迭代器?咱们来测试一下
print('__next__' in dir(range(12))) #查看'__next__'是否是在range()方法执行以后内部是否有__next__ print('__iter__' in dir(range(12))) #查看'__next__'是否是在range()方法执行以后内部是否有__iter__ from collections import Iterator print(isinstance(range(100000000),Iterator)) #验证range执行以后获得的结果不是一个迭代器
for循环的本质:循环全部对象,全都是使用迭代器协议。
(字符串,列表,元组,字典,集合,文件对象)这些都不是迭代器,只不过在for循环式,调用了他们内部的__iter__方法,把他们变成了迭代器
而后for循环调用迭代器的__next__方法去取值,并且for循环会捕捉StopIteration异常,以终止迭代
l=['a','b','c'] #一:下标访问方式 print(l[0]) print(l[1]) print(l[2]) # print(l[3])#超出边界报错:IndexError #二:遵循迭代器协议访问方式 diedai_l=l.__iter__() print(diedai_l.__next__()) print(diedai_l.__next__()) print(diedai_l.__next__()) # print(diedai_l.__next__())#超出边界报错:StopIteration #三:for循环访问方式 #for循环l本质就是遵循迭代器协议的访问方式,先调用diedai_l=l.__iter__()方法,或者直接diedai_l=iter(l),而后依次执行diedai_l.next(),直到for循环捕捉到StopIteration终止循环 #for循环全部对象的本质都是同样的原理 for i in l:#diedai_l=l.__iter__() print(i) #i=diedai_l.next() #四:用while去模拟for循环作的事情 diedai_l=l.__iter__() while True: try: print(diedai_l.__next__()) except StopIteration: print('迭代完毕了,循环终止了') break
序列类型:字符串,列表,元组都有下标,你用上述的方式访问,perfect!可是你可曾想过非序列类型:像字典,集合,文件对象的感觉,因此嘛,年轻人,for循环就是基于迭代器协议提供了一个统一的能够遍历全部对象的方法,即在遍历以前,先调用对象的__iter__方法将其转换成一个迭代器,而后使用迭代器协议去实现循环访问,这样全部的对象就均可以经过for循环来遍历了
Python中提供的生成器:
1.生成器函数:常规函数定义,可是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每一个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
2.生成器表达式:相似于列表推导,可是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
生成器Generator:
本质:迭代器(因此自带了__iter__方法和__next__方法,不须要咱们去实现)
特色:惰性运算,开发者自定义
生成器函数
一个包含yield关键字的函数就是一个生成器函数。yield能够为咱们从函数中返回值,可是yield又不一样于return,return的执行意味着程序的结束,调用生成器函数不会获得返回的具体的值,而是获得一个可迭代的对象。每一次获取这个可迭代对象的值,就能推进函数的执行,获取新的返回值。直到函数执行结束。
import time def genrator_fun1(): a = 1 print('如今定义了a变量') yield a b = 2 print('如今又定义了b变量') yield b g1 = genrator_fun1() print('g1 : ',g1) #打印g1能够发现g1就是一个生成器 print('-'*20) #我是华丽的分割线 print(next(g1)) time.sleep(1) #sleep一秒看清执行过程 print(next(g1))
生成器有什么好处呢?就是不会一会儿在内存中生成太多数据
假如我想让工厂给学生作校服,生产2000000件衣服,我和工厂一说,工厂应该是先答应下来,而后再去生产,我能够一件一件的要,也能够根据学生一批一批的找工厂拿。
而不能是一说要生产2000000件衣服,工厂就先去作生产2000000件衣服,等回来作好了,学生都毕业了。。
def produce(): """生产衣服""" for i in range(2000000): yield "生产了第%s件衣服"%i product_g = produce() print(product_g.__next__()) #要一件衣服 print(product_g.__next__()) #再要一件衣服 print(product_g.__next__()) #再要一件衣服 num = 0 for i in product_g: #要一批衣服,好比5件 print(i) num +=1 if num == 5: break #到这里咱们找工厂拿了8件衣服,我一共让个人生产函数(也就是produce生成器函数)生产2000000件衣服。 #剩下的还有不少衣服,咱们能够一直拿,也能够放着等想拿的时候再拿
更多应用
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)
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))
yield from
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()))
列表推导式和生成器表达式
#老男孩因为峰哥的强势加盟很快走上了上市之路,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 xrange(4))
而不用画蛇添足的先构造一个列表:
sum([x ** 2 for x in xrange(4)])
本章小结
可迭代对象:
拥有__iter__方法
特色:惰性运算
例如:range(),str,list,tuple,dict,set
迭代器Iterator:
拥有__iter__方法和__next__方法
例如:iter(range()),iter(str),iter(list),iter(tuple),iter(dict),iter(set),reversed(list_o),map(func,list_o),filter(func,list_o),file_o
生成器Generator:
本质:迭代器,因此拥有__iter__方法和__next__方法
特色:惰性运算,开发者自定义
使用生成器的优势:
延迟计算,一次返回一个结果。也就是说,它不会一次生成全部的结果,这对于大数据量处理,将会很是有用。
#列表解析 sum([i for i in range(100000000)])#内存占用大,机器容易卡死 #生成器表达式 sum(i for i in range(100000000))#几乎不占内存
有效提升代码可读性