zoukankan      html  css  js  c++  java
  • 迭代器和生成器

    迭代器和生成器

    • 迭代器:

    • 楔子

    • python中的for循环

    • 迭代的概念

    • 可迭代协议和迭代器协议

    • 再谈for循环

    • 生成器:

    • 初始生成器

    • 生成器函数

    一、迭代器---楔子

      我们都知道在对不同的数据类型的变量进行取值时可以有很多办法,比如str类型数据可以通过索引、切片或for循环的方式将所需子串取出;通过列表的索引,切片、方法或者for循环之类的都可以将列表中的元素取出;而元组呢也可以通过索引、切片、for循环的方式取出,字典可以通过key取出对应的value或者使用for循环取出key或value;而集合只能通过for循环取出里面的元素;

      通过上面各数据类型的不同取值方法,我们可以发现他们都有一个共同的取出元素的方法,那就是for循环,而在上面我并没有列出int的取值方法,为什么,因为int不能用for循环来取值,我们来看个例子,通过for循环对一个int数据类型进行循环时会发生什么情况:

    >>> num = 123456
    >>> for n in num:
    ...     print(n)
    ... 
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'int' object is not iterable
    
    #报错了,提示是“类型错误:int对象不可迭代”

      不可迭代?这是什么鬼,什么是迭代呢???并且为什么其他的数据类型就可以执行并且不报错呢?for循环到底是怎样的一个机制呢?for循环是通过数据的索引取值?还是通过切片取值?还是根据什么方法来取值的呢?带着这一系列问题,我们继续往下看!

    二、python中的for循环

      在解释迭代含义之前,我们先来谈一谈python中的for循环;要了解for循环我们还得从代码的角度出发;我们再看一些例子:

    #1.首先我们对一个列表进行for循环
    >>> li = [1,2,3,4,5,6]
    >>> for n in li:
    ...     print(n,end=" ")
    ... 
    1 2 3 4 5 6
    #python成功的帮我们把列表中的元素不多不少的取出来了;
    
    #2.我们再对一个数字进行for循环
    >>> num = 123456
    >>> for n in num:
    ...     print(n)
    ... 
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'int' object is not iterable
    #这时,程序报错了,错误信息是int不是一个可迭代对象
    
    
    #3.通过数字循环报错,我们可以逆向认为,for循环的对象可能得是一个可迭代的对象,那么想整明白for循环,看来我们要先了解一下迭代这个词了;
    python中的for循环

    三、迭代的概念

      定义:迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值;

      也就是说迭代是一个重复的过程,每次重复即一次迭代,并且每次迭代的结果都是下一次迭代的初始值;如果只是单纯地重复,因而不是迭代;比如:

    #单纯的重复
    while True: 
        print('===>') 
    
    
    #迭代
    l=[1,2,3]
    count=0
    while count < len(l):
        print(l[count])
        count+=1

    四、可迭代协议和迭代器协议

      协议说明:假如我们自己写了一个数据类型,希望这个数据类型里的东西也可以使用for被一个一个的取出来,那我们就必须满足for的要求。这个要求就叫做“协议”。

    1.什么是可迭代协议?

      定义:可以被迭代要满足的要求就叫做可迭代协议。可迭代协议的定义非常简单,就是内部实现了__iter__方法。

    2.什么是可迭代对象?

      可迭代对象指的是内置有__iter__方法的对象,即obj.__iter__,也就是说只要这个对象的方法中有__iter__方法,那么我们就叫这个对象为可迭代对象;

    3.如何查看某个类的某个对象的方法中有没有这个__iter__方法呢?

      我们使用一个函数来检查,dir()函数,这是一个python内置的函数,用来打印对象下的所有方法,那好我们就根据上面所说,去查一查列表、元组、字符串、集合、数字等等看看他们的方法下有没有__iter__这个方法;

    #我们将各数据类型下的方法进行取交集,这样的话如果有__iter__的话说明他们都是有这个可迭代方法的;我们先对str、list、tuple、set、dict这几个数据类型进行取交集;
    st = set(dir("str"))
    num = set(dir(123))
    boo = set(dir(True))
    li = set(dir([4,5,"A"]))
    tu = set(dir(("q","w",)))
    dic = set(dir({"K1":"V1"}))
    se = set(dir({1,6,3,9,4}))
    print(st&li&tu&dic&se)
    ------------------- 输出结果 -----------------------------------
    {'__subclasshook__', '__len__', '__gt__', '__dir__', '__sizeof__', '__doc__', '__format__', '__delattr__', '__setattr__', '__hash__', '__init_subclass__', '__iter__', '__class__', '__new__', '__str__', '__lt__', '__repr__', '__ge__', '__init__', '__le__', '__reduce__', '__ne__', '__getattribute__', '__reduce_ex__', '__contains__', '__eq__'}
    
    #可以看出我们对这几个数据类型取交集后得到的结果是有_iter_这个方法的,说明这几个数据类型都是有这个方法的,如果有一个没有,那么这个方法是取不到的;
    
    #接下来我们单独来看看int和bool数据类型下的方法有没有_iter_;
    {'__gt__', '__reduce_ex__', 'denominator', '__ceil__', '__index__', 'numerator', '__floordiv__', '__rshift__', '__rxor__', '__add__', '__new__', '__radd__', '__le__', '__mul__', '__hash__', '__abs__', '__setattr__', '__doc__', '__rpow__', '__rtruediv__', '__and__', '__dir__', 'imag', 'bit_length', '__round__', '__ne__', '__repr__', 'conjugate', '__pow__', '__rdivmod__', 'to_bytes', '__sizeof__', '__rand__', '__divmod__', '__eq__', '__sub__', '__float__', '__rmul__', '__truediv__', '__trunc__', '__init__', '__rfloordiv__', '__neg__', '__lt__', '__delattr__', '__mod__', '__or__', '__rmod__', '__bool__', '__class__', '__rsub__', '__str__', '__xor__', 'real', '__getattribute__', '__init_subclass__', '__rlshift__', '__floor__', 'from_bytes', '__ge__', '__lshift__', '__format__', '__getnewargs__', '__int__', '__ror__', '__subclasshook__', '__rrshift__', '__pos__', '__invert__', '__reduce__'}
    {'__gt__', '__reduce_ex__', 'denominator', '__ceil__', '__index__', 'numerator', '__floordiv__', '__rshift__', '__rxor__', '__add__', '__new__', '__radd__', '__le__', '__mul__', '__hash__', '__abs__', '__setattr__', '__doc__', '__rpow__', '__rtruediv__', '__and__', '__dir__', 'imag', 'bit_length', '__round__', '__ne__', '__repr__', 'conjugate', '__pow__', '__rdivmod__', 'to_bytes', '__sizeof__', '__rand__', '__divmod__', '__eq__', '__sub__', '__float__', '__rmul__', '__truediv__', '__trunc__', '__init__', '__rfloordiv__', '__neg__', '__lt__', '__delattr__', '__mod__', '__or__', '__rmod__', '__bool__', '__class__', '__rsub__', '__str__', '__xor__', 'real', '__getattribute__', '__init_subclass__', '__rlshift__', '__floor__', 'from_bytes', '__ge__', '__lshift__', '__format__', '__getnewargs__', '__int__', '__ror__', '__subclasshook__', '__rrshift__', '__pos__', '__invert__', '__reduce__'}
    
    #我们会发现int和bool里面并没有_iter_方法
    通过dir()内置函数查看对象下的方法

      因为for循环的对象必须是可迭代的,就是说这个对象必须有_iter_这个方法,for循环才会遍历,所以int类型和bool类型不能用在for循环中;

    4.那么这个_iter_方法到底做了什么事情呢?为什么有了这个方法才可以进行for循环?

      我们继续通过代码去了解_iter_方法:

    #我们找一个可迭代对象让其调用_iter_方法,然后再打印一下:
    >>> li = [1,2]
    >>> print(li.__iter__())
    <list_iterator object at 0x7fc167b157f0>

      执行了print(li.__iter__()) 后,我们得到了一个list_iterator,不认识?没关系,我们找度娘问问;

      

      什么情况?一个可迭代对象执行了_iter_方法后竟然生成了一个迭代器,那么,啥是迭代器啊?

    5.什么是迭代器:

      定义:迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前退);

    6.什么是迭代器对象:

      迭代器对象指的是内置有__iter__方法和__next__方法的对象,也就是说只要这个对象的方法中有__iter__和__next__方法,那么我们就叫这个对象为迭代器对象;

      同样我们使用dir()内置函数来查看,首先我们必须要通过可迭代对象生成一个迭代器对象,然后通过dir()这个迭代器对象来查看它下面的方法:

    #我们已经知道list是一个可迭代对象,因为它方法中有_iter_,所以我们用一个列表通过自身的_iter_方法生成一个迭代器对象;
    >>> li = [1,2,3]           #可迭代对象li
    >>> l1 = li.__iter__()  #通过可迭代对象调用iter方法生成的迭代器对象l1
    >>> print(dir(l1))         #查看这个迭代器对象下的方法;
    ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__length_hint__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__']
    查看迭代器对象下的方法

      通过上面的实例,我们可以知道,迭代器对象一定具有__iter__方法和__next__方法,也就是说只要具有这两个方法,我们就可以称这个对象为迭代器对象;

    7.迭代器的特点:

    #优点:
    #1.迭代器提供了一种不依赖于索引的取值方式,这样就可以遍历那些没有索引的可迭代对象了(字典,集合,文件)
    #2.迭代器与列表比较,迭代器是惰性计算的,更节省内存,惰性计算解释:惰性计算就是每次就取一个值,需要我们多次去使用它;
    
    #缺点:
    #1.一次性的,只能往后取值,不能倒着取值;
    #2.无法获取迭代器的长度,使用不如列表索引取值灵活

    8.迭代器的用途,多用在for循环中;

    五、再谈for循环

      那么,我们理解了以上问题后,再来看看for循环是如何配合迭代器来获取数据的;

      for循环的内部机制:

    #1.首先for循环的对象是一个可迭代对象,
    #2.通过可迭代对象下的_iter_方法获取到迭代器,
    #3.得到迭代器后通过迭代器的_next_方法来获取到数据
    #4.将获取到的数据赋值给定义的变量;
    #5.当遇到迭代器取值完毕后的报错后,for循环通过try异常处理来解决,当遇到报错也就是迭代器取值完毕后,就break退出;

      我们可以通过while循环来模拟for循环的操作流程;

    >>> li = [1,2,3,4,5]               #建立一个具有可迭代的对象;
    >>> l1 = li.__iter__()            #将此对象转变为迭代器对象;
    >>> while 1:
    ...     try:
    ...             print(l1.__next__())   #迭代器对象使用next方法取值
    ...     except StopIteration:
    ...             break                       #取值完毕后遇到异常退出
    ... 
    1
    2
    3
    4
    5
    
    ##这样的形式是不是很像for循环那样,其实就是for循环内部实现的机制,不同的是for循环将每次去取到的值进行了变量赋值了;

    六、初识生成器

      我们知道的迭代器有两种:1.直接调用next方法返回的,2.通过可迭代对象执行iter方法得到;

      迭代器好处就是可以节省内存;

      如果在某些情况下,也需要节省内存,就只能自己写。我们自己写的这个能实现迭代器功能的东西就叫生成器;

    生成器创建:

      1.生成器函数

      常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行

      2.生成器表达式

      类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表;

    生成器Generator:

      本质:生成器的本质就是迭代器,所以生成器本省自带了iter和next

      特点:和迭代器一样,惰性运算,节省内存,只能向前,开发者可以自定义;

    生成器函数:

      定义:一个包含yield关键字的函数就是一个生成器函数;

      yield说明:yield可以为我们从函数中返回值,但是yield又不同于return,return的执行意味着程序的结束,调用生成器函数不会得到返回的具体的值,而是得到一个可迭代的对象。每一次获取这个可迭代对象的值,就能推动函数的执行,获取新的返回值。直到函数执行结束。

    import time
    def genrator_fun1():
        a = 1
        print('现在定义了a变量')
        yield a
        b = 2
        print('现在又定义了b变量')
        yield b
    
    g1 = genrator_fun1()
    print('g1 : ',g1)       #打印g1可以发现g1就是一个生成器
    print('-'*20)   #我是华丽的分割线
    print(next(g1))
    time.sleep(1)   #sleep一秒看清执行过程
    print(next(g1))
    
    ---------------------- 输出结果 ------------------------
    g1 :  <generator object genrator_fun1 at 0x00000000021EF728>
    --------------------
    现在定义了a变量
    1
    现在又定义了b变量
    2
    初识生成器函数

    生成器的好处。

      就是不会一下子在内存中生成太多数据,就是节省内存;

      比如:我想让工厂给学生做校服,生产2000000件衣服,我和工厂一说,工厂应该是先答应下来,然后再去生产,我可以一件一件的要,也可以根据学生一批一批的找工厂拿。
    而不能是一说要生产2000000件衣服,工厂就先去做生产2000000件衣服,等回来做好了,学生都毕业了。。。

    #初识生成器二
    
    def produce():
        """生产衣服"""
        for i in range(2000000):
            yield "生产了第%s件衣服"%i
    
    product_g = produce()
    print(product_g.__next__()) #要一件衣服
    print(product_g.__next__()) #再要一件衣服
    print(product_g.__next__()) #再要一件衣服
    num = 0
    for i in product_g:         #要一批衣服,比如5件
        print(i)
        num +=1
        if num == 5:
            break
    
    #到这里我们找工厂拿了8件衣服,我一共让我的生产函数(也就是produce生成器函数)生产2000000件衣服。
    #剩下的还有很多衣服,我们可以一直拿,也可以放着等想拿的时候再拿
    
    初识生成器二
    初识生成器二

    使用生成器写一个监听文件输入例子:

    import time
    def tail(filename):
        f = open(filename)
        f.seek(0, 2) #从文件末尾算起
        while True:
            line = f.readline()  # 读取文件中新的文本行
            if not line:
                time.sleep(0.1)
                continue
            yield line
    
    tail_g = tail('tmp')
    for line in tail_g:
        print(line)
    生成器监听文件输入的例子

    send关键字

      含义:和next方法一样也是取下一个值,不同的是,send还可以给上一个yield的所在位置进行传值;

      注意:不能在开头使用send,因为没有上一个yield,也不能在最后一个yield使用send,因为已经到结尾了;

    def generator():
        print(123)
        content = yield 1
        print('=======',content)
        print(456)
        yield2
    
    g = generator()
    ret = g.__next__()
    print('***',ret)
    ret = g.send('hello')   #send的效果和next一样
    print('***',ret)
    
    #send 获取下一个值的效果和next基本一致
    #只是在获取下一个值的时候,给上一yield的位置传递一个数据
    #使用send的注意事项
        # 第一次使用生成器的时候 是用next获取下一个值
        # 最后一个yield不能接受外部的值
    def averager():
        total = 0.0
        count = 0
        average = None
        while True:
            term = yield average
            total += term
            count += 1
            average = total/count
    
    g_avg = averager()
    next(g_avg)
    print(g_avg.send(10))
    print(g_avg.send(30))
    print(g_avg.send(5))
    计算移动平均值

    yield from

    def gen1():
        for c in 'AB':
            yield c
        for i in range(3):
            yield i
    
    print(list(gen1()))
    
    def gen2():
        yield from 'AB'
        yield from range(3)
    
    print(list(gen2()))
    
    --------------- 输出打印 -----------------------------------------
    ['A', 'B', 0, 1, 2]
    ['A', 'B', 0, 1, 2]
    yield from
  • 相关阅读:
    2019ICPC徐州 H.Yuuki and a problem
    wprintf 输出中文
    bit数组
    Vs2010 Atl工程手工添加连接点
    dll非模态窗口不响应按钮消息
    VC中给控件添加ToolTip
    在Dialog中添加工具条
    在Dialog中添加状态栏
    Vc添加快捷键
    在VC中调用COM组件的方法
  • 原文地址:https://www.cnblogs.com/zhangjunkang/p/9456319.html
Copyright © 2011-2022 走看看