• python迭代器与生成器(二)


     

    一、什么是迭代?

      迭代通俗的讲就是一个遍历重复的过程。

      维基百科中 迭代(Iteration) 的一个通用概念是:重复某个过程的行为,这个过程中的每次重复称为一次迭代。具体对应到Python编程中就是,对于一个可迭代对象,比如Python中的list、tuple、string、dictionary,set等,使用某种循环结构来遍历其中的元素,这种遍历就是迭代。

    复制代码
     1 #对列表进行遍历!
     2 
     3 l=['a','b','c','d','e']
     4 
     5 #while循环的方式
     6 i=0
     7 while i < len(l):
     8     print(l[i])
     9     i+=1
    10 
    11 #for 循环的方式
    12 for i in range(len(l)):
    13     print(l[i])
    复制代码

    二、迭代器

    1、迭代器定义

      首先先明确 可迭代对象

      在现所学的数据类型中,只有 文件 是迭代器,其他的数据类型:元组,字符串,字典,集合,列表都是 可迭代对象。

    判断是否可迭代:只要判断对象本身是否内置了_iter_方法,那它就是可迭代的。

    1 #可迭代的:只要对象本身有__iter__方法,那它就是可迭代的
    2 d={'a':1,'b':2,'c':3}
    3 d.__iter__ #iter(d) 

    可迭代对象实现了 __iter__ 和 __next__ 方法,这两个方法对应内置函数 iter() 和 next() 。__iter__ 方法返回可迭代对象本身,这使得他既是一个可迭代对象同时也是一个迭代器。

    复制代码
     1 #执行对象下的__iter__方法,就会得到一个返回值,得到的返回值就是迭代器。
     2 # 使用next()就能依次取出迭代器中的值(在迭代器元素个数之内,超出会报错,也是一次性的元素,不可重复取值),这样就不用再依赖下标的方式。
     3 d={'a':1,'b':2,'c':3}
     4 d.__iter__ #iter(d)
     5 
     6 i=d.__iter__()  #返回值,迭代器
     7 
     8 print(i.__next__())  #print(next(i))
     9 print(i.__next__())
    10 print(i.__next__())  #3个元素,依次取三次,由于迭代器中的元素是一次性的,超出个数会报错!
    11 print(i.__next__())
    复制代码

    执行结果如下:

    复制代码
    a
    b
    c
    Traceback (most recent call last):
      File "H:/迭代器.py", line 29, in <module>
        print(i.__next__())
    StopIteration
    复制代码

      迭代器

      迭代器是访问集合中元素的一种方式,从集合中的第一个元素开始访问,直到所有的元素都被访问一遍后结束。迭代器不能回退,只能往前进行迭代。

      迭代器提供了一个统一的访问的接口,只要是定义了iter()方法的对象,就可以使用迭代器进行访问。只要可以进行访问,就能被next()方法调用并不断返回下一个值的对象称为迭代器。换句话说,迭代器对象具有next()方法。

      对于迭代器的理解,可以把迭代器看成是一个数据流,迭代器对象被next()函数调用不断返回下一个数据,直到没有数据时抛出StopIteration错误。

      把这个数据流看做是一个有序序列,但却不能提前知道这个数据流到底有多长,而对于list、tuple等可迭代对象来说,对象的长度是可知的。这也是可迭代对象和迭代器的区别所在。

      StopIteration异常:是迭代终止的信号,迭代器内的内容是有限的,迭代器内元素已全部取完,及取值结束再继续取的话,会报错。

      异常捕捉:迭代器正常执行代码的过程中,print(next(*))  *代表迭代器 会抛出异常。为保证代码的正常运行,此时就用到了 try 和 except。

    1)while:循环

    复制代码
    1 l=['a','b','c','d','e']
    2 i=l.__iter__()
    3 while True:
    4     try:  #监听代码是否会报异常 StopIteration
    5         print(next(i))
    6     except StopIteration:  #判断异常是否为 StopIteration,是break
    7         break
    复制代码

    执行结果如下:

    1 a
    2 b
    3 c
    4 d
    5 e

    2)for 循环 :for 循环本质就是内部封装了迭代器,对 可迭代的对象 进行遍历取值,同时会在遇到异常捕获的时候会自行处理,所以for 循环作用在迭代器上不报错。

    1 d={'a':1,'b':2,'c':3}
    2 print(d.__iter__())
    3 for k in d: #d.__iter__()
    4     print(k)

    执行结果如下:

     执行结果
    复制代码
    1 #集合及文件 for 循环 迭代形式
    2 s={1,2,3,4}
    3 for i in s:
    4     print(i)
    5 #a.txt ="aaaaaa bbb cccc eee ffffff"
    6 with open('a.txt','r') as f:
    7     for line in f:
    8         print(line.strip())
    复制代码

    执行结果如下:

     执行结果

    3)此处注意一个小点:关于文件的认知!   由于文件本身就是一个迭代器,当给文件添加_iter_函数的时候,对其迭代器本身是不冲突的!

    1 f=open('a.txt','r')
    2 print(f)
    3 print(f.__iter__())
    4 #迭代器执行iter,得到的还是迭代器本身!结果不冲突。

    执行结果:

    1 <_io.TextIOWrapper name='a.txt' mode='r' encoding='cp936'>
    2 <_io.TextIOWrapper name='a.txt' mode='r' encoding='cp936'>

    2、为什么要用迭代器
    优点:
      (1)迭代器提供了一种不依赖于索引的取值方式,这样就可以遍历那些没有索引的可迭代对象(字典,集合,文件)。
      (2)迭代器与列表比较,迭代器是惰性计算的,不需要事先准备好集合中的所有元素,仅仅在迭代至某个元素时才计算该元素。适合用于遍历一些大的文件或集合,这样更节省内存。
    缺点:
      (1)永远无法获取迭代器的长度,使用不如列表利用索引取值灵活。
      (2)迭代器中的内容是一次性的,并且用next()取值,只能往前走,不能向后退。

    3、检查

    1)借助用模块查看查看可迭代对象与迭代器对象
        from collections import Iterable,Iterator

    isinstance(数据类型,Iterable) 查看是否是可迭代对象

    复制代码
     1 from collections import Iterable,Iterator
     2 
     3 s='hello'
     4 l=[1,2,3]
     5 t=(1,2,3)
     6 d={'a':1}
     7 set1={1,2,3,4}
     8 f=open('a.txt')
     9 
    10 # 都是可迭代的,看能不能加上_iter_()
    11 s.__iter__()
    12 l.__iter__()
    13 t.__iter__()
    14 d.__iter__()
    15 set1.__iter__()
    16 f.__iter__()
    17 print(isinstance(s,Iterable))
    18 print(isinstance(l,Iterable))
    19 print(isinstance(t,Iterable))
    20 print(isinstance(d,Iterable))
    21 print(isinstance(set1,Iterable))
    22 print(isinstance(f,Iterable))
    复制代码

    执行结果:

    复制代码
    True
    True
    True
    True
    True
    True
    复制代码

    isinstance(数据类型,Iterator) 查看是否是迭代器

    复制代码
     1 #查看是否是迭代器 简单些就是看能不能调用_next_(),除文件,其他的都不能调用。
     2 from collections import Iterable,Iterator
     3 
     4 s='hello'
     5 l=[1,2,3]
     6 t=(1,2,3)
     7 d={'a':1}
     8 set1={1,2,3,4}
     9 f=open('a.txt')
    10 
    11 print(isinstance(s,Iterator))
    12 print(isinstance(l,Iterator))
    13 print(isinstance(t,Iterator))
    14 print(isinstance(d,Iterator))
    15 print(isinstance(set1,Iterator))
    16 print(isinstance(f,Iterator))
    复制代码

    执行结果:

    复制代码
    1 False
    2 False
    3 False
    4 False
    5 False
    6 True
    复制代码

    4、小结

      膜拜如此强大的  for 循环!!!看能否for循环遍历,就能知道是否是可迭代对象……吊炸天!

      可以使用for循环进行迭代的对象都是可迭代(Iterable)类型!可以调用next()方法的对象都是迭代器(Iterator)类型!

      next(迭代器),就能取迭代器中的值,每次只执行一次,从头开始向前取一个值。取多个就要用到多个next()(超出迭代器中值的个数会报错)或是for循环。

      python 给字典内置,就将证明可迭代。只要有_iter_函数,加()就能运行,将字典的keys重新赋值,生成一个返回值,将返回值重新定义就生成了一个迭代器。

    三、生成器

    1、定义:

      关于生成器,可以解释为带有yield的函数就被称为生成器。带有yield的函数不再是一个普通函数,不同于while 死循环、for 循环这种一次性创建完整的庞大的序列打印输出,他在循环的过程中值是不断推算不断生成的,一边循环一边计算的机制。可以理解成:加入了yield函数的循环,所有的执行过程都会在yield函数这里停顿进行判定或是开始,当执行一周再次走到yield函数这里时,会再次停顿进行判定或是开始。

      值得注意的是,生成器是可迭代对象,也是迭代器对象。生成器的本质,就是将函数做成了一个迭代器,取名为生成器

    2、生成器与return有何区别?

      1)return只能执行一次函数就彻底结束了,而yield能返回多次值。

      2)生成器就是一个函数,这个函数内包含有yield这个关键字

      3)由于生成器是函数类型的迭代器,可以用next() 分步触发函数,也可以for循环。

        ①next()触发的情况!

    复制代码
     1 from collections import Iterator
     2 #生成器就是一个函数,这个函数内包含有yield这个关键字
     3 def test():
     4     print('one')
     5     yield 1 #return 1
     6     print('two')
     7     yield 2 #return 2
     8     print('three')
     9     yield 3 #return 3
    10     # print('four')
    11     # yield 4 #return 4
    12     # print('five')
    13     # yield 5 #return 5
    14 
    15 g=test()  #函数运行返回一个值
    16 print(g)  #打印这个值,显示是生成器类型
    17 print(isinstance(g,Iterator))  #查看类型  是否是迭代器
    18 # g.__iter__()   #可以使用iter()函数
    19 # g.__next__()   #可以使用next()函数打印值
    20 #next()函数会触发生函数的运行,next()一次就触发一次打印一个值。
    21 print(next(g))# next()函数触发生函数运行,打印一个值。
    22 print(next(g))# next()函数触发生成器运行,打印下个值。
    23 print(next(g))# next()函数触发生成器运行,打印下个值。
    24 # print(next(g))# next()函数触发生成器运行,打印下个值。
    25 # print(next(g))# next()函数触发生成器运行,打印下个值。
    复制代码

    执行结果如下:

    复制代码
    1 <generator object test at 0x00000000025CB6D0>
    2 True
    3 one
    4 1
    5 two
    6 2
    7 three
    8 3
    复制代码

    ②for循环

    复制代码
     1 def test():
     2     print('one')
     3     yield 1 #return 1
     4     print('two')
     5     yield 2 #return 2
     6     print('three')
     7     yield 3 #return 3
     8 g=test()
     9 for i in g:
    10     print(i)
    复制代码

    执行结果与上边一致。

    3、yield 函数到底干了什么事情:
      1)yield 把函数变成生成器 ---> 迭代器;
      2)用return 返回只能返回一次,而yield返回多次;
      3)函数在暂停以及继续下一次运行时的状态,是由yield保存。

    复制代码
     1 def countdown(n):
     2     print('start coutdown')
     3     while n > 0:
     4         yield n #1
     5         n-=1
     6     print('done')
     7 g=countdown(5)
     8 print(g)
     9 #for 循环方式
    10 for i in g: #iter(g)
    11     print(i)
    12 # while 循环方式
    13 # while True:
    14 #     try:
    15 #         print(next(g))
    16 #     except StopIteration:
    17 #         break
    18 #由于迭代器内的参数是一次性的,输出二选一,while注意要判定异常。
    19 #
    复制代码

    执行结果:

    复制代码
    1 <generator object countdown at 0x000000000260B258>
    2 start coutdown
    3 5
    4 4
    5 3
    6 2
    7 1
    8 done
    复制代码

    4、yield函数应用

      1)实现 linux 中 tail -f /tmp/a.txt 的功能(及监听文件,实时刷新文件的动态,插入内容即刻打印显示,没有就暂停在当前位置)

    复制代码
     1 import time
     2 def tail(file_path):
     3     with open(file_path,'r') as f:  #以读的方式打开文件
     4         f.seek(0,2)  #读取最后一行的文本
     5         while True:  #循环,一直判断
     6             line=f.readline()  #一次读一行
     7             if not line:   #判断,没有值的话,停顿0.3秒,再回去
     8                 time.sleep(0.3)
     9                 continue
    10             else:
    11                 # print(line,end='')  #打印一行,此行结尾的换行不打印。不用yield函数的话。
    12                 yield line  # 有文本的话,打印文本,然后停在当前位置
    13 g=tail('/tmp/a.txt')  #文件路径
    14 print(next(g))   # 打印   实时监听
    15 # for line in g:
    16 #     print(line)
    复制代码

    2)实现 linux 中 tail -f /tmp/a.txt |grep 'error'的功能(及监听文件,实时刷新文件的动态,实现过滤的功能,及添加进去的内容有'error'打印显示,没有不显示)

    形象的比喻下:数据流就像水流一样,在一个管道上源源不断的从左往右一直传值。有带着标签的数据流就显示出来,没有就不显示。

    复制代码
     1 #/usr/bin/env python
     2 import time
     3 #定义阶段:定义俩生成器函数
     4 def tail(file_path):  #管道左边的传值
     5     with open(file_path,'r') as f:
     6         f.seek(0,2)  #一直读取最后一行
     7         while True:
     8             line=f.readline()
     9             if not line:
    10                 time.sleep(0.3)
    11 #                print('====>')
    12                 continue
    13             else:
    14                 #print(line,end='')
    15                 yield line
    16 #管道右边的过滤
    17 def grep(pattern,lines): # 定义函数(参数)分别为:(过滤的内容,左边写入的一行数据流)
    18     for line in lines: #对写入的每行进行遍历循环
    19         if pattern in line:  #判断是否有匹配上的行
    20             yield line  # return line
    21 
    22 #调用阶段:得到俩生成器对象
    23 g1=tail('/tmp/a.txt')   #为管道左边的传值(要操作的文件)一直监听传入的最后一行的数据
    24 g2=grep('error',g1)     #为管道右边的传值(要过滤的数据)
    25 
    26 #next触发执行g2生成器函数   有符合条件的就打印
    27 for i in g2:
    28     print(i)
    复制代码

    5、协程函数

      如果在一个函数内部yield的使用方式是表达式形式的话,如x=yield,那么该函数成为协程函数。

      生成器代码执行过程中,碰到yield 程序就会暂停,既然yield 以表达式的形式出现在函数中,就表明将 yield 暂停时从外部所携带来的值 传给等号左边的变量。

      整个代码再通过next()函数触发,从yield处开始往下走,代码循环一圈之后,又回到yield这儿停止,以此循环。整个代码通过外部的send()函数给yield传值。

    复制代码
     1 #吃包子代码!
     2 def eater(name): #定义一个名称函数 name 为人名
     3     print('%s start to eat food' %name)
     4     food_list=[]  #清单
     5     while True:
     6         food=yield food_list  # 将yeild从外部接收的值传给food
     7         print('%s get %s ,to start eat' %(name,food))
     8         food_list.append(food) #将food添加到清单中
     9     print('done')  #结束
    10 
    11 e=eater('钢蛋') #赋值人名
    12 #print(e)
    13 print(next(e)) # 开始 触发函数,到yield暂停,将当前值存到food_list中,有一个返回值,打印。
    14 print(e.send('包子')) #为yield传值 拿一个值,将值传给yield当前所对应的变量 food
    15 print(e.send('韭菜馅包子'))#为yield传值
    16 print(e.send('大蒜包子'))#为yield传值
    复制代码

    执行结果:

    复制代码
    1 钢蛋 start to eat food
    2 []
    3 钢蛋 get 包子 ,to start eat
    4 ['包子']
    5 钢蛋 get 韭菜馅包子 ,to start eat
    6 ['包子', '韭菜馅包子']
    7 钢蛋 get 大蒜包子 ,to start eat
    8 ['包子', '韭菜馅包子', '大蒜包子']
    复制代码
  • 相关阅读:
    mybatis之衣服商城
    mybatis之增加操作
    There is no getter for property named 'answer' in 'class (N1)
    java.lang.ClassNotFoundException原因
    Openstack(Kilo)安装系列之环境准备(一)
    Python标识符
    Python命令行参数
    Python中文编码
    嵌入式数据库H2的安装与配置
    saltstack之nginx部署
  • 原文地址:https://www.cnblogs.com/xuaijun/p/7617828.html
Copyright © 2020-2023  润新知