迭代器/生成器函数及协程函数的编写和使用

迭代器/生成器函数及协程函数的编写和使用

返回首页

  迭代器函数:

    迭代器的本质是用来迭代的。迭代就是更新换代。但是他的本质是逐条出结果。

     让所有数据类型,都有一种不依赖下标就可以迭代的方式,这个方式就是迭代器。

     迭代器,一定要是可迭代的对象。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 --------------

 

posted @ 2020-09-20 10:00  王先生是胖子  阅读(232)  评论(0编辑  收藏  举报