迭代器VS生成器
Posted welljoy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了迭代器VS生成器相关的知识,希望对你有一定的参考价值。
迭代器,python里提供类似装饰器一样的一种语法。
# 依赖下标循环的方法
l = [‘a‘,‘b‘,‘c‘,‘d‘,‘e‘]
i = 0
while i < len(l):
print(l[i])
i+=1
# for循环形式迭代
for i in range(len(l)):
print(l[i])
迭代器
只要对象本身有__iter__方法,那它就是可迭代的,只要执行这个方法,它的返回值就是迭代器,这个返回值就有个__next__方法
dic = {‘a‘:1,‘b‘:2,‘c‘:3}
print(dir(dic))
i = dic.__iter__()
print(dir(i))
print(i.__next__())
print(i.__next__())
print(i.__next__())
print(i.__next__())
# 当取的值多于元素本身会抛出StopIteration错误,也可以理解为终止信号
# 为避免爆出异常可以这么做
i = iter(d)
while True:
try:
print(print(next(i))
excet StopIteration:
break
# python里面的for循环的方式不是按照下标而是将你传入的对象变成迭代器,去__next__
在文件中,文件句柄即时迭代器,也是可迭代对象
为什么要用迭代器
- 如果像字典,集合,这种无序的又或者文件这种没有索引的,你没有办法像下标那样的取值
- 迭代器的取值方式是统一的,大家都是按照next的方式取值,迭代器的方式取值占内存比列表这种索引取值更节省内存,他next()一下才会生成一个值属于惰性计算。
迭代器缺点
- 迭代器无法统计有多长,只有到最后一步才能知道多长。指定取值的话,必须一步步的取值下去才能取到。所以使用不灵活
- 迭代器是一次性取值,不能回头。
了解就好了哈
查看可迭代对象
from collections import Iterable,Iterator
s = "hello"
l = [1,2,3]
t = (1,2,3)
d = {‘a‘:1}
set1 = {1,2,3,4}
f = open(‘a.txt‘)
s.__iter__()
l.__iter__()
t.__iter__()
d.__iter__()
set1.__iter__()
f.__iter__()
f.__iter__()
print(isinstance(s,Iterable))
生成器
- 生成器就是一个函数,这个函数内包含有yield这个关键字,生成器是用来生成值的。 生成器也是一种迭代器所以可以 next(g)。==把函数做成一个迭代器,这种函数就叫做生成器。==
- 生成器与return的区别,return只能执行一次而yield可以执行多次,返回多次值
- yield 是把函数变成了迭代器,它使函数可迭代,函数也能够拥有yield使用
from collections import Iterator
def test():
print(‘first‘)
yield 1
yield 2
yield 3
g= test()
print(g) #g是一个函数
print(isinstance(g,Iterator)) #判断类型
print(next(g))
print(next(g))
for i in g:
print(i)
得到
<generator object test at 0x02FC84B0>
True
first
1
2
3
通过debug得知
print(next(g)) #打印first和1,first是test()打印的,1是print(next(g)打印的
print(next(g)) #打印2
for i in g: #打印3,因为此时迭代器已经走到了第3个yield
print(i)
注意以下例子,函数内带了while循环,上面那个例子是不带while循环的
生成流程案例
def test(n):
print(‘start‘)
while n>0:
yield n
n -= 1
print(‘done‘)
g = test(6)
for i in g:
print(i)
生成器场景案例
如果文件内没有error就一直打印====>
如果有error 就打印error那行
如果文件在末尾添加了error,也会打印出来
这是一个监控程序,可以监控log日志,如果日志新添加了error告警,那么print(i)就可以替换成其他的东西,比如发短信等
import time
#定义阶段:定义俩生成器函数
def tail(file_path):
with open(file_path,‘r‘) as f:
f.seek(0,2) #移动到最后一行,2表示以文件末尾为原点进行计算
while True:
line=f.readline()
if not line: #如果监控到空行,没有那一行了就执行这里
time.sleep(0.3)
print(‘====>‘) #这里打印表示他在一直监控这个文件,程序不是卡死
continue
else:# 如果这行是有内容的就执行这里,并返回line
yield line
def grep(pattern,lines):
for line in lines:
if pattern in line:
yield line
#调用阶段:得到俩生成器对象
g1=tail(‘a.txt‘) #打开要监控的文件,得到生成器对象
g2=grep(‘error‘,g1) #输入关键字,得到生成器对象
#next触发执行g2生成器函数
for i in g2:
print(i)
通过debug可知,grep成为了生成器,可以被for循环,i 等于g2里面返回的line
g2里面的line怎么生成,由g1生成,于是运行tail函数
如果把f.seek(0,2)去掉,那么就是从第一行开始,line就是有内容的,(如果保留seek,那就是认为当文件更新了最后一行的内容,添加了新的日志之后,line就有内容了),于是函数家就会到else: yield line
然后这个line会送到grep里面判断if pattern in line,如果匹配,那就输出line
如果不匹配,那就在grep里面的for循环继续执行,继续跳到tail去生成line
于是for i in g2:的i 就是line
然后for循环继续执行,找下一个i是啥
继续执行g2 的for循环 跳到 tail生成line,但是此时最后一行为空,那么就不停的print(‘====>‘),continue 到 line=f.readline() ,直到又有新的日志添加保存到文件之后,才执行yield line
我一开始对with open的理解有问题
我以为是with open 一次,就打开一次,然后数据就存内存里面不再变化
如果文件有过变化,保存后,还得再with open一次
实际上不是这样的,而是如果文件对象f被调用到了,那就会自动再将变化后的东西更新到内存里面,相当于每调用一次文件对象f,就执行了一次with open 操作
这样当日志文件更新被保存后,新的日志内容就能进入 line = f.readline() 里面了
协程
协程是用到.send方法,它会去给yield传个值,达到管道的作用,他的作用和next方法相似,但是多了一个传值的步骤
def eater(name)
print(‘%s start to eat food‘ %(name)
while True:
food = yield
print(‘%s get %s ,to start eat‘%(name,food)
print(‘done‘)
name_yield = eater(‘alex‘)
next(e)
name_yield.send(‘包子‘)
name_yield.send(‘烧麦‘)
name_yield.send(‘饺子‘)
def eater(name)
print(‘%s start to eat food‘ %(name)
food_list = []
while True:
food = yield food_list
print(‘%s get %s ,to start eat‘%(name,food)
food_list.append(food)
print(‘done‘)
name_yield = eater(‘alex‘)
next(e)
name_yield.send(‘包子‘)
name_yield.send(‘烧麦‘)
name_yield.send(‘饺子‘)
以上是关于迭代器VS生成器的主要内容,如果未能解决你的问题,请参考以下文章