迭代器 生成器
一 迭代器
一 迭代的概念
#迭代器即迭代的工具,那什么是迭代呢?
#迭代是一个重复的过程,每次重复即一次迭代,并且每次迭代的结果都是下一次迭代的初始值(每次循环依赖上一次循环的结果) while True: #只是单纯地重复,因而不是迭代 print('===>') l=[1,2,3] count=0 while count < len(l): #迭代 print(l[count]) count+=1
二 为何要有迭代器?什么是可迭代对象?什么是迭代器对象?
#1、为何要有迭代器? 对于序列类型:字符串、列表、元组,我们可以使用索引的方式迭代取出其包含的元素。
但对于字典、集合、文件等类型是没有索引的,若还想取出其内部包含的元素,则必须找出一种不依赖于索引的迭代方式,这就是迭代器。
利用for循环来遍历字典、集合、文件。
#2、什么是可迭代对象? 可迭代对象指的是内置有__iter__方法的对象,即obj.__iter__,如下 'hello'.__iter__ (1,2,3).__iter__ [1,2,3].__iter__ {'a':1}.__iter__ {'a','b'}.__iter__ open('a.txt').__iter__ #3、什么是迭代器对象?遵循可迭代协议: 可迭代对象执行obj.__iter__()得到的结果就是迭代器对象 而迭代器对象指的是即内置有__iter__又内置有__next__方法的对象 文件类型是迭代器对象 open('a.txt').__iter__() open('a.txt').__next__() #4、注意: 迭代器对象一定是可迭代对象,而可迭代对象不一定是迭代器对象。
如字典列表等,for循环可以执行
三 迭代器对象的使用
dic={'a':1,'b':2,'c':3} iter_dic=dic.__iter__() #得到迭代器对象,迭代器对象即有__iter__又有__next__,但是:迭代器.__iter__()得到的仍然是迭代器本身 iter_dic.__iter__() is iter_dic #True print(iter_dic.__next__()) #等同于next(iter_dic) print(iter_dic.__next__()) #等同于next(iter_dic) print(iter_dic.__next__()) #等同于next(iter_dic) # print(iter_dic.__next__()) #抛出异常StopIteration,或者说结束标志 #有了迭代器,我们就可以不依赖索引迭代取值了 iter_dic=dic.__iter__() while 1: try: k=next(iter_dic) print(dic[k]) except StopIteration: break #这么写太丑陋了,需要我们自己捕捉异常,控制next,python这么牛逼,能不能帮我解决呢?能,请看for循环
#基于for循环,我们可以完全不再依赖索引去取值了 dic={'a':1,'b':2,'c':3} for k in dic: print(dic[k]) #for循环的工作原理:基于迭代器模式工作 #1:执行in后对象的dic.__iter__()方法,得到一个迭代器对象iter_dic #2: 执行next(iter_dic),将得到的值赋值给k,然后执行循环体代码 #3: 重复过程2,直到捕捉到异常StopIteration,结束循环
但while不能
五 迭代器的优缺点
#优点: - 提供一种统一的、不依赖于索引的迭代方式 - 惰性计算,节省内存 #缺点: - 无法获取长度(只有在next完毕才知道到底有几个值) - 一次性的,只能往后走,不能往前退
l=['die','erzi','sunzi','chongsunzi'] #全放内存 iter_l=l.__iter__() #只得到迭代器对象,指向内存地址,便于传输中 print(iter_l) # 方法一,直接用iter_l.__next__() print(iter_l.__next__()) print(iter_l.__next__()) print(iter_l.__next__()) print(iter_l.__next__()) print(iter_l.__next__()) # 方法二:用next()来取值 # next()为python中内置的函数,就是在调用iter_l.__next__()为数据类型中内置的 print(next(iter_l)) #next()---->iter_l.__next__()
# 1. 迭代器就是可迭代对象
二 生成器(是数据类型,是可迭代对象)
一 什么是生成器:python不同方式的生成器
1.生成器函数:常规函数定义。但使用yield语句而不是return来返回结果。yield一次返回一个结果,在每个结果中间挂起函数状态。以便下次从他离开的地方继续执行。
2.生成器表达式:类似于列表推导。但生成器返回产生结果的一个对象,而不是一次构建一个结果列表
# 自动实现了迭代器协议,生成器自动就具有next,
# 1.生成器函数形式:
#只要函数内部包含有yield关键字,那么函数名()的到的结果就是生成器,并且不会执行函数内部代码 def func(): print('====>first') yield 1 # 相当于了return,但可以执行多次;而且yield可以保存状态。 print('====>second') yield 2 print('====>third') yield 3 print('====>end') g=func() ##得到生成器对象 print(g) #<generator object func at 0x0000000002184360>
# 此时函数还没有运行,并没有返回值,只是得到了生成器
print(g._next_()) #====>first 1 调用next执行生成器函数
print(g._next_()) #2 从第一次执行完的位置继续,直到碰到yield
#生成器调用next函数的原理:执行函数,直到碰到一个yield,就返回一个值。
#紧接着再执行一次,就在上一次的基础上继续执行直到遇到下一个yield。
二 生成器就是迭代器
# 因此生成器包含以下两功能:封装起来
g.__iter__ g.__next__ #2、所以生成器就是迭代器,因此可以这么取值 res=next(g) print(res)
1. 生成器表达式就是将列表解析中的[ ]变成()
2. 生成器表达式和列表解析都是一种便利的编程方式,只不过生成器更省内存。因为是基于next一个个取。
3. python中不但使用迭代器协议,让for循环变得更加通用,大部分内置函数,也是使用迭代器协议访问对象。
基于迭代器:for、map、reduce、filter、sum等
#列表解析表达式:
g_l=[i for i in the range(10)] #中括号
#生成器表达式:
g_l=(i for i in the range(10)) #圆括号
l=['a' for i in range(10)] #表示循环十次,每次都放入一个a到列表当中
#python的内置函数sum()使用迭代器访问对象,而生成器实现迭代器协议。 sum(x **2 for x in xrange(4)) #而不用先构造一个列表,占内存:
sum[1,2,4,5,6,7]
#如果列表很大,必定占内存
生成器特点:1、语法上和函数类似:几乎一样,除了函数用return,生成器用yield
2、自动实现迭代器协议:从而可以调用next方法,并且没有值返回时,生成器自动产生Stopiteration异常
3、自动挂起:生成器使用yield返回一个值。yield语句挂起该生成器函数的状态,保留足够的信息,以便之后从它离开的地方继续执行
4、生成器只能遍历一次
生成器优点:
#####例1:生孩子 import time def test(): print('开始生孩子啦。。。。。。') print('开始生孩子啦。。。。。。') print('开始生孩子啦。。。。。。') yield '我' #return time.sleep(3) print('开始生儿子啦') yield '儿子' time.sleep(3) print('开始生孙子啦') yield '孙子' res=test() print(res) print(res.__next__()) #test() print(res.__next__()) #test() print(res.__next__()) #test() ########### 例2:卖包子 # 传统方法 def product_baozi(): ret=[] for i in range(100): ret.append('一屉包子%s' %i) return ret baozi_list=product_baozi() print(baozi_list) # 利用生成器 def product_baozi(): for i in range(100): print('正在生产包子') yield '一屉包子%s' %i #i=1 #做好一个,有人要就直接给他了 print('开始卖包子') pro_g=product_baozi() baozi1=pro_g.__next__() # 正在生产包子 (光标停在yield那一行) #加代码 baozi2=pro_g.__next__() # 开始卖包子 正在生产包子 (从yield后面一行开始,然后进入下一轮循环,第二次遇到yield循环) ########## 例3:下蛋 def xiadan(): ret=[] for i in range(10000): #缺点1:列表要足够大 ret.append('鸡蛋%s' %i) return ret print(xiadan()) #缺点1:占空间大 #缺点2:效率低,需要执行完,才能取给别人 ####改进: def xiadan(): for i in range(5): yield '鸡蛋%s' %i alex_lmj=xiadan() print(alex_lmj.__next__()) print(alex_lmj.__next__()) print(alex_lmj.__next__()) print(alex_lmj.__next__()) print(alex_lmj.__next__()) print(alex_lmj.__next__()) # 优点1:延迟计算。一次返回一个结果。不会一次性生成所有的结果。有利于大数据量处理。 ######### 不用一次性全部弄出来,节省内存。来一个给一个,提高效率 # 优点2:生成器还能有效提高代码可读性 # 优点3:更加简洁,保证可读性的前提下尽可能减少代码 for jidan in alex_lmj: #for循环了生成器。python中的for才能实现把类似函数的循环了 print(jidan) jidan=alex_lmj.__next__() jidan=alex_lmj.__next__() jidan=alex_lmj.__next__() jidan=alex_lmj.__next__() jidan=alex_lmj.__next__() jidan=alex_lmj.__next__() print('zhoushaochen 取鸡蛋',jidan)
人口问题:
# 统计出五个省人口分别占全体的比例,'人口普查'文件内容为: {'name':'北京','population':10} {'name':'山东','population':1000000} {'name':'山西','population':31} {'name':'河北','population':3110330000} {'name':'台湾','population':311030330} def get_polulation(): with open('人口普查', 'r', encoding='utf-8') as f: for i in f: yield i #函数的方法是(效率低): ret.append(i) return ret g=get_polulation() # g.__next__() # g.__next__() # g.__next__() # g.__next__() # print(g.__next__()['population']) #取出文件的记录,全是字符串,没办法操作 # s1=eval(g.__next__()) #利用eval将字符串中类型取出来生成一个字典 # print(type(s1)) #检查发现为<class 'dict'> # print(s1['population']) #10 res=0 # for p in g: # p_dic=eval(p) # print(p_dic['population']) # res+=p_dic['population'] # print(res) #上述步骤可以直接用sum来替代 all_pop=sum(eval(i)['population'] for i in g) #前面不能有调用next,否则这里不能取全部的 print(all_pop) # for p in g: #此g在sum循环的时候就已经迭代完了,此步骤无法执行了 # print('%s %%' %eval(p)['population']/all_pop) #获取每个省的人口信息
例:生产者—消费者模型 #_*_coding:utf-8_*_ __author__ = 'Alex Li' #通过生成器实现协程并行运算,单线程实现了并发 # 生产过程和消费过程是并发的,同时运行 import time def consumer(name): #生成器函数 print("%s 准备吃包子啦!" %name) while True: baozi = yield 1 print("包子[%s]来了,被[%s]吃了!" %(baozi,name)) def producer(name): c = consumer('A') c2 = consumer('B') #添加用户B print(c.__next__()) #此处第一次调用函数停在yield1的位置 print(c2.__next__()) print("老板开始准备做包子啦!") for i in range(10): #能多次选择你想要的事物,利用for循环和send time.sleep(1) #模拟生产包子需要的时间 print("做了2个包子!") c.send(i) #此处第二次调用函数执行yield1下一行的打印操作 c2.send(i) producer("alex") #####补充知识:send可以触发函数运行,并传值给yield def test() print('开始了') yield 1 print('第一次') yield 2 print('第二次') t=test() print(t._next_()) #或者print(next(t)) # 开始了 # 1 t.send(None) #send里一定要传值,哪怕写None。又在上一次next的基础上执行了一次函数 # 第一次 # yield 第一个特性:类似return # t.send()执行效果和t._next_()一样。但此时None将会替代原来的yield返回值1, def test() print('开始了') f = yield 1 #yield后面的1作为函数返回值,但给f的是send传给yield的值 print('第一次',f) yield 2 print('第二次') # yield另外一个特性:f= yield接收send传过来的一个值 t=test() print(t._next_()) #停在yield 1 t.send(None) #从yield继续执行,send中的传给yield,然后传给f。 # 开始了 # None print(t._next_()) #2
三 练习
1、自定义函数模拟range(1,7,2)
2、模拟管道,实现功能:tail -f access.log | grep '404'
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
#题目一: def my_range(start,stop,step=1): while start < stop: yield start start+=step #执行函数得到生成器,本质就是迭代器 obj=my_range(1,7,2) #1 3 5 print(next(obj)) print(next(obj)) print(next(obj)) print(next(obj)) #StopIteration #应用于for循环 for i in my_range(1,7,2): print(i) #题目二 import time def tail(filepath): with open(filepath,'rb') as f: f.seek(0,2) while True: line=f.readline() if line: yield line else: time.sleep(0.2) def grep(pattern,lines): for line in lines: line=line.decode('utf-8') if pattern in line: yield line for line in grep('404',tail('access.log')): print(line,end='') #测试 with open('access.log','a',encoding='utf-8') as f: f.write('出错啦404\n')
四 协程函数
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
#yield关键字的另外一种使用形式:表达式形式的yield def eater(name): print('%s 准备开始吃饭啦' %name) food_list=[] while True: food=yield food_list print('%s 吃了 %s' % (name,food)) food_list.append(food) g=eater('egon') g.send(None) #对于表达式形式的yield,在使用时,第一次必须传None,g.send(None)等同于next(g) g.send('蒸羊羔') g.send('蒸鹿茸') g.send('蒸熊掌') g.send('烧素鸭') g.close() g.send('烧素鹅') g.send('烧鹿尾')
五 练习
1、编写装饰器,实现初始化协程函数的功能
2、实现功能:grep -rl 'python' /etc
![](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
#题目一: def init(func): def wrapper(*args,**kwargs): g=func(*args,**kwargs) next(g) return g return wrapper @init def eater(name): print('%s 准备开始吃饭啦' %name) food_list=[] while True: food=yield food_list print('%s 吃了 %s' % (name,food)) food_list.append(food) g=eater('egon') g.send('蒸羊羔') #题目二: #注意:target.send(...)在拿到target的返回值后才算执行结束 import os def init(func): def wrapper(*args,**kwargs): g=func(*args,**kwargs) next(g) return g return wrapper @init def search(target): while True: filepath=yield g=os.walk(filepath) for dirname,_,files in g: for file in files: abs_path=r'%s\%s' %(dirname,file) target.send(abs_path) @init def opener(target): while True: abs_path=yield with open(abs_path,'rb') as f: target.send((f,abs_path)) @init def cat(target): while True: f,abs_path=yield for line in f: res=target.send((line,abs_path)) if res: break @init def grep(pattern,target): tag=False while True: line,abs_path=yield tag tag=False if pattern.encode('utf-8') in line: target.send(abs_path) tag=True @init def printer(): while True: abs_path=yield print(abs_path) g=search(opener(cat(grep('你好',printer())))) # g.send(r'E:\CMS\aaa\db') g=search(opener(cat(grep('python',printer())))) g.send(r'E:\CMS\aaa\db')
六 yield总结
#1、把函数做成迭代器 #2、对比return,可以返回多次值,可以挂起/保存函数的运行状态
# 生成器只能迭代一次 问题1: def test() for i in range(4): yield i t=test() #for i in t # print(i) #得到0 1 2 3 t1=(i for i in t) # t1也是一个生成器 #for i in t1 # print(i) #得到0 1 2 3 print(list(t1)) #遍历生成器,放入到一个列表中[0,1,2,3] #情况2: t=test() for i in t print(i) t1=(i for i in t) #由于t只能被遍历一遍,此处t1什么都读不到 print(list(t1)) #0 #1 #2 #3 #[] #情况3: t=test() #此时t中什么都没有 t1=(i for i in t) #取出没有遍历过的t t2=(i for i in t) print(list(t1)) #0 1 2 3 此时把t1当中的值被取完了, print(list(t2)) #[] t2就什么都取不到了 #生成器在产生的时候,实质t中什么都没有
三 面向过程编程
#1、首先强调:面向过程编程绝对不是用函数编程这么简单,面向过程是一种编程思路、思想,而编程思路是不依赖于具体的语言或语法的。言外之意是即使我们不依赖于函数,也可以基于面向过程的思想编写程序 #2、定义 面向过程的核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么 基于面向过程设计程序就好比在设计一条流水线,是一种机械式的思维方式 #3、优点:复杂的问题流程化,进而简单化 #4、缺点:可扩展性差,修改流水线的任意一个阶段,都会牵一发而动全身 #5、应用:扩展性要求不高的场景,典型案例如linux内核,git,httpd #6、举例 流水线1: 用户输入用户名、密码--->用户验证--->欢迎界面 流水线2: 用户输入sql--->sql解析--->执行功能
ps:函数的参数传入,是函数吃进去的食物,而函数return的返回值,是函数拉出来的结果,面向过程的思路就是,把程序的执行当做一串首尾相连的功能,该功能可以是函数的形式,然后一个函数吃,拉出的东西给另外一个函数吃,另外一个函数吃了再继续拉给下一个函数吃。。。
posted on 2018-04-02 23:14 Josie_chen 阅读(109) 评论(0) 编辑 收藏 举报