zoukankan      html  css  js  c++  java
  • (八)迭代器,列表生成式和生成器

    迭代器定义

    对于list、string、tuple、dict等这些容器对象,使用for循环遍历是很方便的。在后台for语句对容器对象调用iter()函数。iter()是python内置函数。
    iter()函数会返回一个定义了next()方法的迭代器对象,它在容器中逐个访问容器内的元素。next()也是python内置函数。在没有后续元素时,next()会抛出一个StopIteration异常,通知for语句循环结束。
    迭代器是用来帮助我们记录每次迭代访问到的位置,当我们对迭代器使用next()函数的时候,迭代器会向我们返回它所记录位置的下一个位置的数据。实际上,在使用next()函数的时候,调用的就是迭代器对象的_next_方法(Python3中是对象的_next_方法,Python2中是对象的next()方法)。所以,我们要想构造一个迭代器,就要实现它的_next_方法。但这还不够,python要求迭代器本身也是可迭代的,所以我们还要为迭代器实现_iter_方法,而_iter_方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的_iter_方法返回自身self即可。

    几个概念

    1,迭代器协议:对象需要提供next()方法,它要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代。
    2,可迭代对象:实现了迭代器协议的对象。list、tuple、dict都是Iterable(可迭代对象)。但可以使用内建函数iter(),把这些都变成Iterable(可迭代器对象),以遵循迭代器协议,并拥有next()方法。
    3,for item in Iterable 循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束.

    一个栗子

    x = 'lan'
    iter_test = x.__iter__()      //iter_test就是通过iter()函数获取的可迭代对象x的迭代器
    print(iter_test)              //对获取到的迭代器不断调用next()方法来获取下一个值
    print(iter_test.__next__())   //也可以写成next(iter_test),__next__()是对象的内置方法,next()是Python内置的方法
    print(iter_test.__next__())
    print(iter_test.__next__())   //如果再调用一次next(),将抛出StopIteration的异常

    结果:<str_iterator object at 0x00000236CF732A90>
       l
       a
       n

    for循环的本质

    以上栗子用for循环

    x = 'lan'
    for i in x:
        print(i)

    所以,string、list、tuple、dict这些都不是迭代器对象,只不过在for循环中,调用了他们内部的__iter__() 方法,使他们变成了迭代器对象,而拥有了next()方法。

    用while去模拟以上的for循环:

    x = 'lan'
    i_x = x.__iter__()
    while True:
        try:
            print(i_x.__next__())
        except StopIteration:
            print('迭代结束了,循环终止')
            break

    所以当我们使用for循环去遍历对象的时候,本质上是做了两件事:

    1.调用对象内部的__iter__() 方法,使他们变成了迭代器对象,对获取到的迭代器不断调用next()方法,直至取到最后一个元素。

    2.当元素都迭代完了,遇到StopIteration异常的时候,帮我们处理异常。

     强大的for循环

    对于序列类型的对象,像字符串、列表、元组等,都有下标,我们可以用下面的方式遍历:

    x = 'lan'
    index = 0
    while index < x.__len__():
        print(x[index])
        index += 1

    但是,非序列类型的对象,像字典、集合、文件对象,就无法用上述方式去遍历,只能依靠for循环,例如对文件对象的遍历:

    f = open('123.txt')
    i_f = f.__iter__()
    print(i_f.__next__(),end='')
    print(i_f.__next__(),end='')

    所以,for循环就是基于迭代器协议提供了一个统一的可以遍历所有对象的方法,即在遍历之前,先调用对象的__iter__()方法使其变成迭代器,然后使用迭代器协议去实现循环访问,这就是无所不能的for循环。

    三元表达式

    a = 'aaa'
    res = '哈哈' if a == 'bbb' else '嘿嘿'
    print(res)
    结果:嘿嘿
    if a == 'bbb'是第一元,'哈哈' 是第二元,'嘿嘿' 是第三元
    如果a == 'bbb',那么res = '哈哈',否则res = '嘿嘿'

    列表生成式

    l = []
    for i in range(10):               //普通方式
        l.append(i)
    print(l)
    
    l1 = [i for i in range(10)]       //列表生成式
    print(l1)

    生成器

    什么是生成器?

    可以理解为一种数据类型,这种数据类型自动实现了迭代器协议(而其他的数据类型需要调用自己内置的__iter__方法才能实现迭代器协议),所以生成器本身就是可迭代对象。

    迭代器可以节省大量的内存空间,通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

    所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(Generator)。

    定义generator(生成器)的两种方法:

    #两种方式
    #函数的方式
    def test():
        yield 1             //yield的作用:1.和return一样返回一个值,并结束这次函数的运行。 2.保存当前的状态,当下一次next(g)或者g.__next__()的时候会从上一次的位置继续运行,直到遇到下一个yield
    g = test()
    print('来自函数',g)
    #生成器表达式,只要把一个列表生成式的[]改成(),就创建了一个generator
    g = (i for i in range(10))
    print('来自生成器表达式',g)
    结果:来自函数 <generator object test at 0x000002AFB974B990>
         来自生成器表达式 <generator object <genexpr> at 0x000002AFB98790A0>

     既然生成器是一种数据类型,又自动实现了迭代器协议,生成器本身又是可迭代对象,那么怎么遍历生成器呢?三种方式:

    def test():
        for i in range(5):
            # print(i)
            yield i
    
    t = test()
    print(t)               //这时t就是一个生成器,但是这只是一个内存地址,里面没有任何值,每去遍历一次,才多一个值
    
    print(next(t))         //第一种方式,next(),执行一次多一个值
    print(next(t))
    print(next(t))
    print(next(t))
    print(next(t))
    
    for i in t:            //第二种方式,用for循环去遍历这个生成器,所有的值挨个取完
        print(i)
    
    print(list(t))         //第三种方式,用list、sum、reduce等方式去取,也是取完

    结果:

    <generator object test at 0x000001E31666A410>
    0
    1
    2
    3
    4
    []

    注意:生成器只能遍历一次!如上方式二和三就没有取到值,因为方式一已经遍历了。

  • 相关阅读:
    Linux2_软件源apt 仓库 包 的概念
    I2C(smbus pmbus)和SPI分析
    Linux1_发型版本(Debian RHEL)与软件包管理器(RPM dpkg)的概念
    ubuntu系统下如何切换输入法
    【NYOJ】[169]素数
    【杭电】[2020]绝对值排序
    【杭电】[2020]绝对值排序
    【NYOJ】[199]无线网络覆盖
    【NYOJ】[243]字母统计
    【NYOJ】[198]数数
  • 原文地址:https://www.cnblogs.com/xulan0922/p/10069904.html
Copyright © 2011-2022 走看看