迭代器、生成器、面向过程编程
一、介绍
本章主要介绍如下内容:
- 迭代器
- 生成器
- 面向过程编程
二、迭代器
2.1 什么是迭代器
迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。迭代器修改了常规指针的接口,所谓迭代器是一种概念上的抽象:那些行为上像迭代器的东西都可以叫做迭代器。然而迭代器有很多不同的能力,它可以把抽象容器和通用算法有机的统一起来。
什么是迭代? 指的是一个重复的过程,每一次重复称为一次迭代,并且每一次重复的结果是下一次重复的初始值。 # while True: # print('=====>') #只是单纯地重复,因而不是迭代 # l=['a','b','c'] # count=0 # while count < len(l): #迭代 # print(l[count]) # count+=1
2.2 为何要有迭代器?什么是可迭代对象?什么是迭代器对象?
#1、为何要有迭代器? 对于序列类型:字符串、列表、元组,我们可以使用索引的方式迭代取出其包含的元素。但对于字典、集合、文件等类型是没有索引的,
若还想取出其内部包含的元素,则必须找出一种不依赖于索引的迭代方式,这就是迭代器 #2、什么是可迭代对象?
可迭代对象指的是内置有__iter__方法的对象,如下
# name='egon'
# l=[1,2,3] # t=(1,2,3) # d={'name':'egon','age':18,'sex':'male'} # s={'a','b','c'} # f=open('a.txt','w',encoding='utf-8') # name.__iter__ #可迭代 # l.__iter__ #可迭代 # t.__iter__#可迭代 # d.__iter__#可迭代 # s.__iter__ #可迭代 # f.__iter__ #可迭代 #3 迭代器对象(文件是):
可迭代对象执行obj.__iter__()得到的结果就是迭代器对象 而迭代器对象指的是即内置有__iter__又内置有__next__方法的对象
#obj.__iter__,obj.__next__
# f.__iter__
# f.__next__ #总结: #1 可迭代对象不一定是迭代器对象 #2 迭代器对象一定是可迭代的对象 #3 调用obj.__iter__()方法,得到的是迭代器对象(对于迭代器对象,执行__iter__得到的仍然是它本身)
2.3 迭代器对象的使用
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循环
2.4 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,结束循环
###########
#1、调用in后的obj_iter=obj.__iter__()
#2、k=obj_iter.__next__()
#3、捕捉StopIteration异常,结束迭代
# d={'name':'egon','age':18,'sex':'male'}
# for k in d:
# print(k)
2.5 迭代器的优缺点
#优点: #1、提供一种统一的、不依赖于索引的取值方式,为for循环的实现提供了依据 #2、迭代器同一时间在内存中只有一个值——》更节省内存, #缺点: #1、只能往后取,并且是一次性的 #2、不能统计值的个数,即长度 # l=[1,2,3,4,5,6] # l[0] # l[1] # l[2] # l[0] # l_iter=l.__iter__() # # print(l_iter) # print(next(l_iter)) # print(next(l_iter)) # print(next(l_iter)) # print(next(l_iter)) # print(next(l_iter)) # print(next(l_iter)) # print(next(l_iter)) # # l_iter=l.__iter__() # print(next(l_iter)) # print(next(l_iter)) # print(next(l_iter)) # print(len(l_iter))
三、生成器
3.1 什么是生成器
#只要函数内部包含有yield关键字,那么函数名()的到的结果就是生成器,并且不会执行函数内部代码 def func(): print('====>first') yield 1 print('====>second') yield 2 print('====>third') yield 3 print('====>end') g=func() print(g) #<generator object func at 0x0000000002184360>
3.2 生成器就是迭代器
def func(): print('=====>1') yield 1 print('=====>2') yield 2 print('=====>3') yield g=fun() g.__iter__ g.__next__ #所以生成器就是迭代器,因此可以这么取值 res=next(g) print(res)
3.3
1、自定义函数模拟range(1,7,2) 2、模拟管道,实现功能:tail -f access.log | grep '404' #题目一: 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')
3.4 协程函数
#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('烧鹿尾')
3.5 小练习
1、编写装饰器,实现初始化协程函数的功能 2、实现功能:grep -rl 'python' /etc #题目一: 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')
#分析二: # 第一步:拿到一个文件夹下所有的文件的绝对路径 import os def init(func): def inner(*args,**kwargs): g=func(*args,**kwargs) next(g) return g return inner @init def search(target): # r'D:\video\python20期\day4\a' while True: filepath = yield g = os.walk(filepath) for pardir, _, files in g: for file in files: abs_path = r'%s\%s' % (pardir, file) #把abs_path传给下一个阶段 target.send(abs_path) # 第二步:打开文件拿到文件对象f @init def opener(target): while True: abs_path = yield with open(abs_path,'rb') as f: #把(abs_path,f)传给下一个阶段 target.send((abs_path,f)) #第三步:读取f的每一行内容 @init def cat(target): while True: abs_path,f=yield for line in f: #把(abs_path,line)传给下一个阶段 res=target.send((abs_path,line)) #满足某种条件,break掉for循环 if res: break #第四步:判断'python' in line @init def grep(target,pattern): pattern = pattern.encode('utf-8') res=False while True: abs_path,line=yield res res=False if pattern in line: #把abs_path传给下一个阶段 res=True target.send(abs_path) #第五步:打印文件路径 @init def printer(): while True: abs_path=yield print('<%s>' %abs_path) g=search(opener(cat(grep(printer(),'python')))) #'python' in b'xxxxx' g.send(r'D:\video\python20期\day4\a')
3.6 总结
#yield的功能: #1、yield为我们提供了一种自定义迭代器对象的方法 #2、yield与return的区别 1:yield可以返回多次值 2:函数暂停与再继续的状态是由yield帮我们保存的
四、面向过程编程
#1、首先强调:面向过程编程绝对不是用函数编程这么简单,面向过程是一种编程思路、思想,而编程思路是不依赖于具体的语言或语法的。言外之意是即使我们不依赖于函数,也可以基于面向过程的思想编写程序 #2、定义 面向过程的核心是过程二字,过程指的是解决问题的步骤,即先干什么再干什么 基于面向过程设计程序就好比在设计一条流水线,是一种机械式的思维方式 #3、优点:复杂的问题流程化,进而简单化 #4、缺点:可扩展性差,修改流水线的任意一个阶段,都会牵一发而动全身 #5、应用:扩展性要求不高的场景,典型案例如linux内核,git,httpd #6、举例 流水线1: 用户输入用户名、密码--->用户验证--->欢迎界面 流水线2: 用户输入sql--->sql解析--->执行功能