zoukankan      html  css  js  c++  java
  • python 基础:迭代器、生成器

    一、迭代器
    1、迭代器介绍
    迭代器即用来迭代取值的工具,而迭代是重复反馈过程的活动,其目的通常是为了逼近所需的目标或结果,每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值,单纯的重复并不是迭代

    goods=['mac','lenovo','acer','dell','sony']
    
    index=0
    while index < len(goods):
        print(goods[index])
        index+=1
    

    下述while循环才是一个迭代过程,不仅满足重复,而且以每次重新赋值后的index值作为下一次循环中新的索引进行取值,反复迭代,最终可以取尽列表中的值。
    注意:

    上面这个迭代方法只适合序列类型,并不适合字典、集合、文件对象的迭代取值。
    有人会说for循环可以对字典、文件对象、集合进行循环取值,其实for循环用到的是迭代器,因此这里没有用for循环为例说明,后面会具体说迭代器和可迭代对象的区别与联系。

    (1)可迭代对象
    通过索引的方式进行迭代取值,实现简单,但仅适用于序列类型:字符串,列表,元组。对于没有索引的字典、集合、文件对象等非序列类型,必须找到一种不依赖索引来进行迭代取值的方式,这就用到了迭代器。

    可迭代对象(Iterable)。从语法形式上讲,内置有__iter__方法的对象都是可迭代对象,字符串、列表、元组、字典、集合、打开的文件都是可迭代对象。

    {'name':'吴晋丞'}.__iter__
    {7,8,9}.__iter__
    ……
    

    (2)迭代器对象
    调用obj.iter()方法返回的结果就是一个迭代器对象(Iterator)。
    除文件对象外,字典、集合、列表等都只是可迭代对象而已,它们可以通过调用iter方法生成迭代器对象,并进行迭代取值。但文件对象在python中使用时,直接生成的就是迭代器对象,当然它也是可迭代对象。文件对象可以直接调用next函数,只有迭代器对象才能调用next方法,因此文件对象是迭代器对象。执行迭代器对象.iter()方法得到的仍然是迭代器本身,而执行迭代器对象.next()方法就会计算出迭代器中的下一个值。

    >>> s={1,2,3} # 可迭代对象s
    >>> i=iter(s)  # 本质就是在调用s.__iter__(),返回s的迭代器对象i,
    >>> next(i) # 本质就是在调用i.__next__()
    1
    >>> next(i)
    2
    >>> next(i)
    3
    >>> next(i)  #抛出StopIteration的异常,代表无值可取,迭代结束,要想继续取值,除非在重新生成一个迭代器。
    
    

    为什么文件对象要直接设置成迭代器对象,集合、字典等不设置成迭代器对象呢?

    答:首先我们应该明白迭代器这种需要一个值,next方法取一个值,这样是降低了内存的消耗的。集合、字典等定义下来,本来就不是用来存很多、很多东西的。但是一个文件对象却可以很大很大,因此必须直接生成迭代器对象,控制内存的消耗。

    2、for循环原理
    有了迭代器后,我们便可以不依赖索引迭代取值了,使用while循环的实现方式如下

    dic = {'a':1,'b':2,'c':3}
    d_iterator = iter(dic) #d_iterator便是一个迭代器对象,iter方法生成的就是一个迭代器对象
    while True: # while循环的循环体是进行循环迭代取值的
        try:
            print(next(d_iterator))
        except(StopIteration): # 捕获异常
            break # 异常处理
    
    

    for循环又称为迭代循环,in后可以跟任意可迭代对象,上述while循环可以简写为:

    dic = {'a':1,'b':2,'c':3}
    for i in dic:
    	print(item)
    
    

    for 循环在工作时,首先会调用可迭代对象dic内置的iter方法拿到一个迭代器对象,然后再调用该迭代器对象的next方法将取到的值赋给i,执行循环体完成一次循环,周而复始,直到捕捉StopIteration异常,结束迭代。

    为什么文件对象中也需要有iter方法呢?
    它不是已经是迭代器对象了吗,而且前面说迭代器调用iter方法屁用没有,得到的还是本身。

    答:这是一个专门为统一for循环而设计的,因为for循环中你不确定自己in后面到底是不是迭代器对象,难道还要判断一下吗?那不是太麻烦了,无论谁来,只要可迭代,我就给你iter方法一下,即使是文件对象,也不影响啥。所以文件对象也需要一个iter方法,拿去让for循环调用。

    3、迭代器的优缺点
    基于索引的迭代取值,所有迭代的状态(迭代的位置信息)都保存在了索引中,而基于迭代器实现迭代的方式不再需要索引,所有迭代的状态就保存在迭代器中,然而这种处理方式优点与缺点并存:

    (1)优点
    为序列和非序列类型提供了一种统一的迭代取值方式。
    惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值,就迭代器本身来说,同一时刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型,如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。

    (2)缺点
    除非取尽,否则无法获取迭代器的长度
    只能取下一个值,不能回到开始,更像是'一次性的',迭代器产生后的唯一目标就是重复执行next方法直到值取尽,否则就会停留在某个位置,等待下一次调用next;若是要再次迭代同个对象,你只能重新调用iter方法去创建一个新的迭代器对象,如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值。

    二、生成器
    1、生成器与yield
    若函数体包含yield关键字,再调用函数,并不会执行函数体代码,只是得到的返回值即生成器对象

    def my_range(start,stop,step=1):
        while start < stop:
            yield start
            start += step
    g = my_range(1,10,2)
    print(g)
    while True:
        try:
            print(g.__next__()) #yield用于返回值
        except(StopIteration):
            break
    
    

    执行结果:

    生成器内置有__iter__和__next__方法,所以生成器本身就是一个迭代器
    有了yield关键字,我们就有了一种自定义迭代器的实现方式。yield可以用于返回值,但不同于return,函数一旦遇到return就结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值
    2、yield表达式应用
    (1)yield基本应用
    在函数内可以采用表达式形式的yield

    >>> def eater():
    ...     print('Ready to eat')
    ...     while True:
    ...         food=yield
    ...         print('get the food: %s, and start to eat' %food)
    ...
    
    

    可以拿到函数的生成器对象持续为函数体send值,如下

    >>> g=eater() # 得到生成器对象
    >>> g
    <generator object eater at 0x101b6e2b0>
    >>> next(g) # 需要事先”初始化”一次,让函数挂起在food=yield,等待调用g.send()方法为其传值
    Ready to eat
    >>> g.send('包子')
    get the food: 包子, and start to eat
    >>> g.send('鸡腿')
    get the food: 鸡腿, and start to eat
    
    

    针对表达式形式的yield,生成器对象必须事先被初始化一次,让函数挂起在food=yield的位置,等待调用g.send()方法为函数体传值,send不仅是对yield传值,还有next方法的作用,传值为None那么g.send(None)等同于next(g)。

    (2)装饰器初始化生成器

    def init(func):
        def wrapper(*args,**kwargs):
            g=func(*args,**kwargs) # 函数中有关键字yield,因此函数不会执行,而是直接返回一个生成器对象
            next(g) # 这步必须要做,执行函数体代码,并让eater函数挂起在food = yield处
            return g # 返回生成器对象,让用户能够调用,当然这也是装饰器必要的返回值伪装。
        return wrapper
    
    @init
    def eater():
        print('Ready to eat')
        food_list = []
        while True:
            food = yield food_list #左边send传值,右边next或send返回值
            food_list.append(food)
    g = eater()
    g.send('阆中牛肉面')
    g.send('成都冒菜')
    g.send('成都羊肉汤锅')
    res = g.send('酸辣粉')
    print(res)
    
    

  • 相关阅读:
    《软件测试》实验
    作业5
    之前看构建之法老段让我写的随笔
    作业2
    Bookstore系统测试缺陷报告
    实验七——Web应用测试(bookstore项目上完成)
    《构建之法》读后感
    (1)写一个程序,用于分析一个字符串中各个单词出现的频率,并将单词和它出现的频率输出显示。(单词之间用空格隔开,如“Hello World My First Unit Test”); (2)编写单元测试进行测试; (3)用ElcEmma查看代码覆盖率,要求覆盖率达到100%。
    个人简介
    (1)把一个英语句子中的单词次序颠倒后输出。例如输入“how are you”,输出“you are how”; (2)编写单元测试进行测试; (3)用ElcEmma查看代码覆盖率,要求覆盖率达到100%。
  • 原文地址:https://www.cnblogs.com/Eric-6688/p/13975662.html
Copyright © 2011-2022 走看看