迭代器/生成器函数及协程函数的编写和使用
返回首页
迭代器函数:
迭代器的本质是用来迭代的。迭代就是更新换代。但是他的本质是逐条出结果。
让所有数据类型,都有一种不依赖下标就可以迭代的方式,这个方式就是迭代器。
迭代器,一定要是可迭代的对象。Python解释器会为迭代器类型的数据内置一个iter方法。
迭代器可以不依赖下标也可以取值。
只要有__iter__方法,就是可迭代的对象。
d = {"a":2,"b":8,"l":4,}
## 可迭代的:只要对象本身有__iter__方法,那它就是可迭代的。
H = d.__iter__() #有iter方法,就是可迭代的 iter(d) 返回值H就是我们要的迭代器
print(H.__next__()) #迭代器本身有一个next方法,可以获取一个d字典的key。一个next就可以获取一次值。
print(H.__next__())
print(H.__next__())
在next的取值的方法中,如果next的次数超过数据类型的长度,会报stopIteration的异常错误。也可以理解为是一个结束信号。
while循环取字典的值,迭代器方式:
d={'a':1,'b':2,'c':3}
i=iter(d)
while True:
try:
print(next(i))
except StopIteration:
break
while循环取列表的值,迭代器方式:
l=['a','b','c','d','e']
i=l.__iter__()
while True:
try:
print(next(i))
except StopIteration:
break
为什么要用迭代器:
优点
1:迭代器提供了一种不依赖于索引的取值方式,这样就可以遍历那些没有索引的可迭代对象了(字典,集合,文件)。
2:迭代器与列表比较,迭代器是惰性计算的,更节省内存。
缺点
1:无法获取迭代器的长度,使用不如列表索引取值灵活。
2:一次性取值,只能往后取值,不能倒着取值,取过就不能再去,先用迭代器在用for循环是取不到值的。
查看可迭代对象与迭代器对象:需要使用到collections模块的lterable方法和lterator方法。lterable是否可迭代,lterator是否是迭代器。
生成器函数:
生成器就是一个函数,但是这个函数包含一个yield关键字。
生成器本身就是一个迭代器,但是它不直接叫迭代器是因为生成器是将函数做成了迭代器。
判断这个生成器是否是迭代器,用collection模块的iterator方法去判断一下。
def test():
print("first")
yield 1 #yield 1 和return 1 很像。
g = test() #执行函数,不会有返回值,但是可以拿到这个值。
print(g) #打印的结果是一个内存地址,这个内存地址指向的是一个生成器对象generator。
print(isinstance(g,iterator))
print(next(g)) #执行test函数,得到的不是函数的执行效果,而是拿到了一个生成器。next生成器才会触发函数的运行。
生成器函数,一个yield,只会出一个次结果。
def countdown(n):
print('start coutdown')
while n > 0:
yield n #1
n-=1
print('done')
g=countdown(5) #拿到一个countdown的生成器
print(g)
print(next(g)) #一次next,取一次值。
print(next(g))
print(next(g))
print(next(g))
print(next(g))
print(next(g))
但是next的次数大过取值范围,就会报stopIteration的异常错误。
当next抛出异常是,将next捕获,做异常处理。
用while处理异常:
def countdown(n):
print('start coutdown')
while n > 0:
yield n #1
n-=1
print('done')
g=countdown(5) #拿到一个countdown的生成器
print(g)
while True:
try:
print(next(g)) #捕获next的异常
except StopIteration:
break
用for也可以处理异常:
def countdown(n):
print('start coutdown')
while n > 0:
yield n #1
n-=1
print('done')
g=countdown(5) #拿到一个countdown的生成器
print(g)
for i in g: #iter(g) 每一次for循环都是在遍历g的迭代器,然后next取值。
print(i)
生成器和return的区别:
return只能返回一次函数就彻底结束了,而yield能返回多次值。
yield到底干了什么事情:
yield把函数变成生成器-->迭代器
相当于把__iter__ 和 __next__ 方法封装到函数内部
用return返回值能返回一次,而yield返回多次
函数在暂停以及继续下一次运行时的状态是由yield保存
生成器的使用场景:动态查看日志文件的变化 tail -f
开两台Linux,查看一下tail -f /var/log/nginx/access.log 日志。
在node001节点:touch /tmp/a.txt
在node001节点:tail -f /tmp/a.txt 动态检测文件内容的变化,有新增内容就打印出来。
在node002节点:echo "hello" >> /tmp/a.txt
这时在node001节点,就可以看到node002追加进文件的内容。
用生成器实现tail命令的效果,本质是读文件新增的内容并打印。
在node001节点:vim tail.py
import time
def tail(file_path):
with open(file_path,'r') as f:
f.seek(0,2)
while True:
line=f.readline() #读不到值,就要判断
if not line: #没读到值
time.sleep(0.3)
print("===>")
continue
else: #读到值
# print(line)
yield line
g=tail('/tmp/a.txt')
print(next(g)) #监听到一行就yield结束
#for line in g:
# if “error” in line:
# print(line) #for循环是一直监听
在node001节点:python tail.py 这样就等待读取了。
在node002节点:echo "hello world" >> /tmp/a.txt
在node001节点:tail -f /tmp/a.txt | grep "error" #当读到的信息包含error才打印。
在node002节点:这时在echo有error的值,是打印的,echo没有error的值,是不打印的。
import time
#定义阶段
def tail(file_path):
with open(file_path,'r') as f:
f.seek(0,2)
while True:
line=f.readline() #读不到值,就要判断
if not line: #没读到值
time.sleep(0.3)
continue
else: #读到值
# print(line)
yield line
def grep(pattern,lines):
for line in lines:
if pattern in line:
yield line
#调用阶段 得到两个生成器对象
g1 = tail("/tmp/a.txt")
g2 = grep("error",g1)
#使用生成器,next触发执行g2生成器函数
for i in g2:
print(i)
协程函数:
如果在一个函数内部yield的使用方式是表达式形式的话,如x=yield,那么该函数成为协程函数。
def eater(name):
print('%s start to eat food' %name)
food_list=[]
while True:
food=yield food_list #yield的表达式形式
print('%s get %s ,to start eat' %(name,food))
food_list.append(food)
# print('done')
e=eater('George')
# print(e)
print(next(e))
print(e.send('东坡肉')) #send会把值给yield,yield再把值给food
print(e.send('红烧排骨'))
print(e.send('锅包肉'))
e.send 和 next(e) 的区别:
1、如果函数内yield是表达式形式,那么必须先next(e)
2、二者的共同之处是都可以让函数在上次暂停的位置继续运行,
不一样的地方在于send在触发下一次代码的执行中,会顺便给yield传一个值。
给协程函数加装饰器:
def init(func):
def wrapper(*args,**kwargs):
res = func(*args,**kwargs)
next(res)
return res
return wrapper
@init
def eater(name):
print("%s start to eat" % name)
food_list =[]
while True:
food = yield food_list
print("%s eat %s" %(name,food))
food_list.append(food)
e = eater("hb") #wrapper("hb")
next(e)
print(e.send("123"))
print(e.send("123"))
print(e.send("123"))
print(e.send("123"))
print(e.send("123"))
作业:
爬网页,一直yield
from urllib.request import urlopen
def get():
while True:
url = yield
res = urlopen(url).read()
print(res)
g = get()
next(g)
g.send("http://www.python.org")
用协程函数实现grep -rl 的操作。
#grep -rl 'root' /etc
import os,time
def wrapper(func):
def inner(*args,**kwargs):
res = func(*args,**kwargs)
next(res)
return res
return inner
@wrapper
def search(target):
"""
找到文件的绝对路径
target是生成器对象
:return:
"""
while True:
dir_name = yield #协程函数表达式,将路径传给dir_name
print('车间search开始生产产品:文件路径')
time.sleep(2)
g = os.walk(dir_name)
for i in g:
for j in i[-1]:
file_path = '%s/%s' %(i[0],j)
target.send(file_path) #将路径传出去。找到一个发一个。
# print(file_path)
@wrapper
def opener(target):
"""
打开一个文件,获取文件的句柄。
:return:
"""
while True:
file_path = yield #接到路径
print('车间opener开始生产产品:文件句柄')
time.sleep(2)
with open(file_path) as f: #接到文件路径,打开文件
target.send(f(file_path,f)) #send可以传多个值,但是要是元组的形式。
@wrapper
def cat(target):
"""
读取文件内容
:return:
"""
while True:
file_path,f = yield #那到文件句柄,读文件
print('车间cat开始生产产品:文件内容')
time.sleep(2)
for line in f: #读文件。
target.send((file_path,line))
@wrapper
def grep(pattern,target):
"""
过滤一行内容里是否有关键字。
:return:
"""
while True:
file_path,line = yield
print('车间grep开始生产产品:文件关键字')
time.sleep(2)
if pattern in line:
target.send(file_path)
@wrapper
def printer():
"""
打印有关键字的文件路径
:return:
"""
while True:
file_path = yield
print('车间printer开始生产产品:文件路径')
time.sleep(2)
print(file_path)
g = search(opener(cat(grep('python',printer()))))
# next(g)
g.send("/etc")
列表生成式:
列表生成式的语法是:列表里面有for循环,并且有if判断。
[
expression for item1 in iterable1 if condition1
for item2 in iterable2 if condition2
.....
for itemN in iterableN if conditionN
]
eg:
l=[1,2,3,4]
s='hello'
# l1=[(num,s1) for num in l if num > 2 for s1 in s]
# print(l1)
l1=[]
for num in l:
for s1 in s:
t=(num,s1)
l1.append(t)
print(l1)
也可以用列表生成式实现grep -rl的操作。
import os
g=os.walk('C:\test')
file_path_list=[]
for i in g:
# print(i)
for j in i[-1]:
file_path_list.append('%s\%s' %(i[0],j))
print(file_path_list)
g=os.walk('C:\test')
l1=['%s\%s' %(i[0],j) for i in g for j in i[-1]]
print(l1)
生成器表达式:
语法形式:生成器的语法格式和列表推导式类似,将[ ] 换成( )。
(
expression for item1 in iterable1 if condition1
for item2 in iterable2 if condition2
.....
for itemN in iterableN if conditionN
)
优点是:省内存,一次只产生一个值在内存中。
应用:读取一个大文件的所有内容,并且处理行。
g=l=('egg%s' %i for i in range(1000000000000000000000000000000000000))
print(g)
print(next(g))
print(next(g))
for i in g:
print(i)
eg1:将文件中,每一行的空格都处理掉。
#常规处理方法:弊端是大文件处理时,内存就暴了。
f=open('a.txt')
l=[]
for line in f:
line=line.strip()
l.append(line)
print(l)
#用列表生成器的形式是实现:这样依旧会占据内存空间
l1=[line.strip() for line in f]
print(l1)
#用生成器的形式实现:一次next取一次值,减少内存压力
g=(line.strip() for line in f)
print(g)
print(next(g))
eg2:计算文件中商品的价格。
b.txt文件
apple 10 3
Mercedes-G-AMG 3000000 2
Mac 30000 1
Porsche911 3000000 3
计算价格:
money_l=[]
with open('b.txt') as f:
for line in f:
goods=line.split()
res=float(goods[-1])*float(goods[-2])
money_l.append(res)
print(money_l)
print(sum(money_l))
用生成器的形式实现:
f=open('b.txt')
g=(float(line.split()[-1])*float(line.split()[-2]) for line in f)
print(sum(g))
eg3:模拟数据库查询数据
res=[]
with open('b.txt') as f:
for line in f:
# print(line)
l=line.split() #切成列表
# print(l)
d={} #字典
d={"name":None,"price":None,"count":None} #定义字典格式
d['name']=l[0]
d['price']=l[1]
d['count']=l[2]
res.append(d)
print(res)
用声明式方式简化:
with open('b.txt') as f:
res=(line.split() for line in f)
print(res)
dic_g=({'name':i[0],'price':i[1],'count':i[2]} for i in res)
print(dic_g)
apple_dic=next(dic_g)
print(apple_dic['count'])
apple_dict=next(dic_g)
print(apple_dict)
取出单价大于1万的:
#取出单价>10000
with open('b.txt') as f:
res=(line.split() for line in f)
# print(res)
dic_g=({'name':i[0],'price':i[1],'count':i[2]} for i in res if float(i[1]) > 10000)
print(dic_g)
print(list(dic_g))
# for i in dic_g:
# print(i)
---------------- END --------------