迭代器
可迭代的数据类型
可迭代的:凡是可以被for循环的都是可迭代的 例如:list dic str set tuple range() enumerate(枚举) f=open()(文件句柄)
查看数据类型的所有方法
dir(数据类型)
print(dir([]))
print(dir({}))
可迭代协议:
只要含有__iter__()方法的就是可迭代
凡是可以被for循环的数据类型就一定含有__iter__()方法
迭代器协议和可迭代对象
1.迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前退) 2.可迭代对象:实现了迭代器协议的对象(如何实现:对象内部定义一个__iter__()方法) 3.协议是一种约定,可迭代对象实现了迭代器协议,python的内部工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象。
判断一个数据类型是否是迭代器和可迭代对象:
from collections import Iterable from collections import Iterator print(isinstance([],Iterator))#True 如果值为True就是迭代器 print(isinstance([],Iterable))#True 判断是否为可迭代对象如果值为True就是可迭代对象
迭代器协议的原理
1 #基于迭代器协议 2 li = [1,2,3] 3 diedai_l = li.__iter__() 4 print(diedai_l.__next__()) 5 print(diedai_l.__next__()) 6 print(diedai_l.__next__()) 7 # print(diedai_l.__next__()) # 超出边界报错 8 9 #下标 10 print(li[0]) 11 print(li[1]) 12 print(li[2]) 13 # print(li[3]) # 超出边境报错 14 15 # 用while循环模拟for循环机制 16 diedai_l = li.__iter__() 17 while True: 18 try: 19 print(diedai_l.__next__()) 20 except StopIteration: 21 print("迭代完毕,循环终止") 22 break 23 24 # for循环访问方式 25 # for循环本质就是遵循迭代器协议的访问方式,先调用diedai_l=li.__iter__方法 26 # 或者直接diedai_l=iter(l),然后依次执行diedai_l.__next__(),直到捕捉到 27 # StopItearation终止循环 28 # for循环所有的对象的本质都是一样的原理
#序列类型 字符串,列表,元组都有下标,你用上述的方式访问
#非序列类型 字典,集合,文件
#for循环就是基于迭代器协议提供了一个统一的可以遍历所有对象的方法,
#即在遍历之前,先调用对象的__iter__方法将其转换成一个迭代器,
#然后使用迭代器协议去实现循环访问,这样所有的对象就都可以通过for循环来遍历了,
#而且你看到的效果也确实如此,这就是无所不能的for循环,觉悟吧,年轻人
迭代器
同时含有__iter__()方法和__next()__方法的就是迭代器
示例详解
s = [1,2,3,4,5] # 判断s是迭代器还是可迭代对象 from collections import Iterator from collections import Iterable print(isinstance(s,Iterable)) #True 是可迭代对象,有__iter()__方法 print(s.__iter__()) #对应的内存地址 print(isinstance(s,Iterator)) #False 不是迭代器 没有__next__()方法 res = s.__iter__() print(isinstance(res,Iterator)) #True 是迭代器 既有__iter()__又有__next()__方法 print(res.__next__()) # __next__方法一次只能取一个值,想要取多个值必须执行多次此方法 print(res.__next__()) print(res.__next__()) print(res.__next__()) print(res.__next__()) print(res.__next__()) #如果执行的__next()__方法次数超出了s本身的长度后会报错 #StopIteration,因为s本身的长度只有5,当执行到第六次__next()__方法的时候就会出现这个错误
为啥要用迭代器
#优点 # 1:迭代器提供了一种不依赖于索引的取值方式,这样就可以遍历那些没有索引的可迭代对象了(字典,集合,文件),一个迭代器只能取所有数据一次
# 2:迭代器与列表比较,迭代器是惰性计算的,更节省内存 #缺点: # 1:无法获取迭代器的长度,使用不如列表索引取值灵活 # 2:一次性的,只能往后取值,不能倒着取值
迭代器的用途
for循环
生成器
什么是生成器
生成器的本质上就是函数 只不过是我们自己写的函数,生成器一定是迭代器 迭代器不一定是生成器
只要函数内部含有yield关键字的就是生成器函数 生成器函数执行之后返回一个生成器作为返回值
可以理解为一种数据类型,这种数据类型自动实现了迭代器协议(其他的数据类型需要调用自己内置的__iter__方法),所以生成器就是可迭代对象
生成器的分类及在python中的表现形式
1.生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
2.生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
为何要使用生成器(生成器的优点)
Python使用生成器对延迟操作提供了支持。所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。
这也是生成器的主要好处(通常所说的惰性运算:只有在需要的时候才会执行且一次只可以得到一个值想要得到多个值必须执行多次)。
惰性运算的优点
1.避免不必要的计算,带来性能的提升。 2.节省空间,使得无线循环的数据结构成为可能
生成器特点
调用函数之后 函数不会执行 会返回一个生成器
每次调用__next__()方法会取到一个值 直到取完最后一个再执行__next__()方法会报错 StopIteration
初识生成器
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)
生成器取值的方法
1.for循环取值:生成器函数调用的时候返回一个生成器 for循环生成器中的每个元素执行出结果
def produce(): for i in range(100): yield '生产了第%s件衣服'%i res = produce() num = 0 for i in res: print(i) num += 1 if num == 10: break 结果: 生产了第0件衣服 生产了第1件衣服 生产了第2件衣服 生产了第3件衣服 生产了第4件衣服 生产了第5件衣服 生产了第6件衣服 生产了第7件衣服 生产了第8件衣服 生产了第9件衣服
2.__next__()方法:生成器函数调用的时候返回一个生成器,生成器.__next__()一次只会取到一个值直到取完最后一个再执行__next__()方法会报错 StopIteration
def produce(): for i in range(4): yield '生产了第%s件衣服'%i res = produce() print(res.__next__()) print(res.__next__()) print(res.__next__()) 结果: 生产了第0件衣服 生产了第1件衣服 生产了第2件衣服 如果取完最后一个值再执行__next__()方法就会报错 def produce(): for i in range(3): yield '生产了第%s件衣服'%i res = produce() print(res.__next__()) print(res.__next__()) print(res.__next__()) print(res.__next__()) 执行结果: Traceback (most recent call last): 生产了第1件衣服 File "D:/python/练习/迭代器和生成器.py", line 17, in <module> 生产了第2件衣服 print(res.__next__()) StopIteration range范围只有3个但是执行了4次__next__()方法
3.数据类型的强制转换
4.send方法
def generator(): print(123) content = yield 1 print('=======',content) print(456) yield2 g = generator() ret = g.__next__() print('***',ret) ret = g.send('hello') #send的效果和next一样 print('***',ret) #send 获取下一个值的效果和next基本一致 #只是在获取下一个值的时候,给上一yield的位置传递一个数据 #使用send的注意事项 # 第一次使用生成器的时候 是用next获取下一个值 # 最后一个yield不能接受外部的值
生成器小结
1.是可迭代对象,返回的是一个生成器对象 2.实现了延迟计算,省内存啊 3.生成器本质和其他的数据类型一样,都是实现了迭代器协议,只不过生成器附加了一个延迟计算省内存的好处,其余的可迭代对象可没有这点好处,记住喽!!!
生成器函数(yield功能)
1.
相当于把__iter__和__next__方法封装到函数内部
2.
与
return
比,
return
只能返回一次,而
yield
能返回多次
3.
函数暂停已经继续运行的状态是通过
yield
保存的
# 用生成器函数 # yield 相当于return控制的是函数的返回值 # x=yield的另外一个特性,接收send传过来的值,赋值给x def test(): print("开始啦") first = yield # return 1 first = None print("第一次",first) yield 2 print("第二次") t = test() print(test().__next__()) res = t.__next__() # next(t) print(res) res = t.send("函数停留在first那个位置,我就是给first赋值的") print(res) 输出结果 开始啦 None 开始啦 None 第一次 函数停留在first那个位置,也就是给first赋值的2
1 #yield表达形式: 2 food=yield 3 4 #生成器.send与next(生成器)区别: 5 1.如果函数内yeild是表达式形式,那么必须先next(e) 6 2.二者的共同之处都可以让函数在上次暂时的位置继续运行不同之处在于send在出发下一次的执行时,会顺便给yield传一个值
二.生成器表达式
print(sum(i for i in range(10000)))
# 表达式一般用for循环 (i for i in range(10000)) # 作用 节省内存,在内部已经实现了__iter__的方法
#三元表达式 name='alex' name='linhaifeng' res='SB' if name == 'alex' else 'shuai' print(res) #列表解析 li = [i for i in range(10) ] li = [i for i in range(10) if i > 5]
各种推导式
列表推导式
#[每一个元素或者是和元素相关的操作 for 元素 in 可迭代数据类型] #遍历之后挨个处理 res = [ i for i in range(10)] #[满足条件的元素相关的操作 for 元素 in 可迭代数据类型 if 元素相关的条件] #筛选功能 ret = [i for i in range(30) if i%3 == 0]
# 例二:找到嵌套列表中名字含有两个‘e’的所有名字 # names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'], # ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']] # ret = [name for lst in names for name in lst if name.count('e') ==2] # print(ret)
字典推导式
# 例一:将一个字典的key和value对调 # mcase = {'a': 10, 'b': 34} # #{10:'a' , 34:'b'} # mcase_frequency = {mcase[k]: k for k in mcase} # print(mcase_frequency) # 例二:合并大小写对应的value值,将k统一成小写 # mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3} # #{'a':10+7,'b':34,'z':3} # mcase_frequency = {k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0) for k in mcase} # print(mcase_frequency)
集合推导式
#集合推导式,自带结果去重功能 # squared = {x**2 for x in [1, -1, 2]} # print(squared)
应用示例
迭代器应用
文件名:a.txt,文件内容如下: apple 10 3 tesla 100000 1 mac 3000 2 lenovo 30000 3 chicken 10 3 实现功能:cat a.txt |grep apple 要求1:定义迭代器函数cat 要求2:定义迭代器函数grep 要求3:模拟管道的功能,将cat的处理结果作为grep的输入
import time #定义迭代器函数cat def cat(file): with open(file) as f: #打开a.txt文档 f.seek(0) #光标移动到文档首行行头 while True: line=f.readline() #按行读取文档 if not line: #采用bool状态,表示读取的这行没有内容 time.sleep(0.2) # print("--------->") continue #结束本次循环,重新开始循环继续读取文档 else: yield line #文档读取的行有内容,则返回改行 #定义迭代器函数grep def grep(args,lines): for line in lines: #相当于多次next(r1) if args in line: #args被赋值“apple”,判断“apple”在不在line这一行内 yield line #“apple”在line这一行内,返回该行 r1=cat("a.txt") r2=grep("apple",r1) for i in r2: #相当于多次print(next(r2)) print(i)
生成器应用
把下述函数改成生成器的形式,执行生成器函数的到一个生成器g,然后每次g.send(url),打印页面的内容,利用g可以无限send def get(url): def index(): return urlopen(url).read() return index
from urllib.request import urlopen def get(): print("开始爬取网页") while True: url=yield #将传入参数赋值给url,可迭代的函数中,yield赋值有表达式就是生成器 print(urlopen("%s"%url).read()) #输出爬取url的内容 print("%s 爬取成功"%url) g=get() #生成迭代器 g.__next__() #触发函数的运行,停留在yield表达式这一行 g.send("http://www.baidu.com") g.send("http://www.python.org")