zoukankan      html  css  js  c++  java
  • 五分钟搞定迭代器生成器

    容器(container)

    容器是一种把多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取。

    python中常见的容器对象

    list、set、tuple、str

    大多数容器都提供了某种方式来获取其中的每一个元素,但是这并不是容器本身提供的能力,而是可迭代对象赋予了容器的这种能力

    可迭代对象(iterable)

    [可迭代对象的内部实现了__iter__方法,该方法返回一个迭代器对象]

    >>> x = [1, 2, 3]
    >>> y = iter(x)
    >>> z = iter(x)
    >>> next(y)
    1
    >>> next(y)
    2
    >>> next(z)
    1
    >>> type(x)
    <class 'list'>
    >>> type(y)
    <class 'list_iterator'>

    迭代器是具有迭代类型,比如list_iteratorset_iterator

    举例说明:

    x = [1, 2, 3]
    for elem in x:
        ...

    真实的情况是:

    反编译该段代码,你可以看到解释器显示地调用GET_ITER指令,相当于调用iter(x)FOR_ITER指令就是调用next()方法,不断地获取迭代器中的下一个元素,但是你没法直接从指令中看出来,因为他被解释器优化过了。

    迭代器(itertor)

    迭代器是一个带状态的对象,任何实现了__iter__ 和 __next__(python2中实现了next())方法的对象都是迭代器。__iter__返回迭代器自身,__next__返回容器中的下一个值,如果容器中没有更多的元素,则抛出异常。

    class Fib:
        def __init__(self):
            self.prev = 0
            self.curr = 1
     
        def __iter__(self):
            return self
     
        def __next__(self):
            value = self.curr
            self.curr += self.prev
            self.prev = value
            return value
     
    >>> f = Fib()
    >>> list(islice(f, 0, 10))
    [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

    Fib既是一个可迭代对象(实现了__iter__方法),又是一个迭代器实现了(__next__方法),实例变量prev和curr用户维护迭代器内部的状态,每一次调用next()方法的时候做两件事:

      1、为下一次调用next()方法修改状态

      2、为当前这次调用生成返回结果

    我的终极实例,助你理解迭代器

    class MyIter:
        def __init__(self, start=0, end=0):
            print('程序的init')
            self.start = start
            self.end = end
        def __iter__(self):
            print('我在这里制造了一个迭代器')
            return self # 这个地方如果不return self 那么c 就不具备可迭代的能力
        def __next__(self):
            print('这里调用 __next__方法', self.start)
            if self.start < self.end:
                i = self.start
                self.start += 1
                return i
            else:
                raise StopIteration()
    c = MyIter(0, 5) # 代码到这里只是创造了一个迭代器
    
    for i in c:# 代码到这里 会调用__iter__和__next__ 具有迭代的能力
        print(i)
    >>>>>>>>>程序的init
    我在这里制造了一个迭代器
    这里调用 __next__方法 0
    0
    这里调用 __next__方法 1
    1
    这里调用 __next__方法 2
    2
    这里调用 __next__方法 3
    3
    这里调用 __next__方法 4
    4
    这里调用 __next__方法 5

    生成器(generator)

    生成器是一种特殊的迭代器,不过这种迭代器显得更加优雅,生成器不需要写__iter__和__next__方法,只需要一个yield关键字。

    生成器一定是迭代器(反之不成立)

    生成器表达式(generator expression)

    生成器表达式是列表推导式的生成器版本

    >>> a = (x*x for x in range(10))
    >>> a
    <generator object <genexpr> at 0x401f08>
    >>> sum(a)
    285

    yield整理(生成器生成的是一个对象,大大节省了内存):

    1 如何去定义一个生成器:

    mygenerator = (i for i in range(10))
    

    2 yield是一个类似return的关键字:

      *return的意义在于在程序中返回某一个值,代码块内部遇到return程序就不会执行下面的代码

      *迭代器遇到yield时就会返回yield后面的值,下一次继续迭代时,从上一次迭代遇到的yield后面的代码(下一行)开始执行。也可以理解成:yield就是return一个值,并且记住这个返回值的位置,下一次迭代就从这个位置往后运行

      *yield的函数不仅仅用于for循环,而且可以用于某一个函数的参数,只要这个函数的参数允许迭代参数

      *send(msg)与next()的区别在于send可以传参给yield表达式,这时传递的参数会作为yield表达式的值,而yield的参数是返回给调用者的值,换句话说:send可以强行修改上一个yield表达式的值,比如函数有一个yield赋值 a = yield 5第一次迭代到这里会返回5,a并没有被赋值,第二次进行迭代时使用.send(10),那么就是强行修改yield 5的表达式的值是10,本来是5,那么a=10

      *send(msg)与next()都有返回值,它们的返回值是当当迭代器遇到yield时,yield表面表达的值,其实就是当前迭代中yield后面的参数

      *第一次调用必须先next()或者send(None),否则会报错,send后之所以是None是因为这时候没有上一个yield数据,可以认为next()等于send(None)

    def foo():
        print("starting...")
        while True:
            res = yield 4, "====="
            print("res:",res)
    g = foo()
    print(g, type(g))
    print(next(g))
    print("*"*20)
    print(next(g))

    输出结果:

    starting...
    (4, '=====')
    ********************
    res: None
    (4, '=====')

    执行步骤:

    1.程序开始执行以后,因为foo函数中有yield关键字,所以foo函数并不会真的执行,而是先得到一个生成器g(相当于一个对象)

    2.直到调用next方法,foo函数正式开始执行,先执行foo函数中的print方法,然后进入while循环

    3.程序遇到yield关键字,然后把yield想想成return,return了一个4之后,程序停止,并没有执行赋值给res操作,此时next(g)语句执行完成,所以输出的前两行(第一个是while上面的print的结果,第二个是return出的结果)是执行print(next(g))的结果,
    4.程序执行print("*"*20),输出20个*

    5.又开始执行下面的print(next(g)),这个时候和上面那个差不多,不过不同的是,这个时候是从刚才那个next程序停止的地方开始执行的,也就是要执行res的赋值操作,这时候要注意,这个时候赋值操作的右边是没有值的(因为刚才那个是return出去了,并没有给赋值操作的左边传参数),所以这个时候res赋值是None,所以接着下面的输出就是res:None,

    6.程序会继续在while里执行,又一次碰到yield,这个时候同样return 出4,然后程序停止,print函数输出的4就是这次return出的4.

    ========================================================================================

    def foo():
        print("starting...")
        while True:
            res = yield 4
            print("res:",res)
    g = foo()
    print(next(g))
    print("*"*20)
    print(g.send(7))

    输出结果:

    starting...
    4
    ********************
    res: 7
    4
    

    先大致说一下send函数的概念:上面那个res的值为什么是None,这个变成了7,到底为什么,这是因为,send是发送一个参数给res的,因为上面讲到,return的时候,并没有把4赋值给res,下次执行的时候只好继续执行赋值操作,只好赋值为None了,而如果用send的话,开始执行的时候,先接着上一次(return 4之后)执行,先把7赋值给了res,然后执行next的作用,遇见下一回的yield,return出结果后结束。

    1.程序开始执行以后,因为foo函数中有yield关键字,所以foo函数并不会真的执行,而是先得到一个生成器g(相当于一个对象)

    2.直到调用next方法,foo函数正式开始执行,先执行foo函数中的print方法,然后进入while循环

    3.程序遇到yield关键字,然后把yield想想成return,return了一个4之后,程序停止,并没有执行赋值给res操作,此时next(g)语句执行完成,所以输出的前两行(第一个是while上面的print的结果,第二个是return出的结果)是执行print(next(g))的结果,
    4.程序执行print("*"*20),输出20个*

    5.程序执行g.send(7),程序会从yield关键字那一行继续向下运行,send会把7这个值赋值给res变量

    6.由于send方法中包含next()方法,所以程序会继续向下运行执行print方法,然后再次进入while循环

    7.程序执行再次遇到yield关键字,yield会返回后面的值后,程序再次暂停,直到再次调用next方法或send方法。

    此处转自:https://blog.csdn.net/mieleizhi0522/article/details/82142856

    总结:

    * 容器是一系列元素的集合,str、list、set、dict、file、sockets都可以看作是容器

      容器都是可以被迭代的

    * 可迭代对象实现了__iter__ 方法,该方法返回一个迭代器对象

    * 迭代器持有一个内部状态字段用户记录下次迭代返回值它实现了 __iter__ 和 __next__方法,迭代器不会一次性把所有的元素加载到内存,而是需要的时候才返回结果

    * 生成器是一种特殊的迭代器,返回值不是通过return,而是通过yeild

  • 相关阅读:
    最近重感冒完全不知道知己在记什么

    倾尽一生
    学习笔记,函数
    唯美句
    02 mysql 基础二 (进阶)
    01 mysql 基础一 (进阶)
    16 正则表达式
    15 迭代器、生成器、模块和包
    14 异常
  • 原文地址:https://www.cnblogs.com/mosson/p/8398187.html
Copyright © 2011-2022 走看看