• python函数4(迭代器、生成器)


    python函数

    一、迭代器

           迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

           1、可迭代对象

           对list、tuple、str等类型的数据可以使用for...in...的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代。
     
           但是是否所有的数据类型都可以放到for...in...的语句中,然后让for...in...每次中取出一条数据供我们使用,即供我们迭代呢?
    for i in 100:
        print(i)
    # TypeError: 'int' object is not iterable
    

      

           2、判断一个对象是否是iterable对象

    from collections.abc import Iterable
    
    a = [1, 3, 4]
    # 判断一个对象是否是可迭代对象
    print(isinstance(a, Iterable))
    

      

           3、可迭代对象的本质

           我们分析对可迭代对象进行迭代使用的过程,发现每迭代一次(即在for in 中每循环一次)都会返回对象中的下一条数据,一直向后读取数据直到迭代了所有数据后结束。那么,在这个过程中就应该有一个“人”去记录每次访问到了第几条记录数据,以便每次迭代都可以返回下一条数据。我们把这个能帮助我们进行数据迭代的“人”称为迭代器(Iterator)。
     
           可迭代对象本质就是可以向我们提供一个这样的中间“人”即迭代器帮助我们对其进行迭代遍历使用。
     
           可迭代对象通过__iter__方法向我们提供一个迭代器,我们在迭代一个可迭代对象的时候,实际上就是先获取该对象提供的一个迭代器,然后通过这个迭代器来依次(__next__方法)获取对象中的每一个数据。一个具备了__iter__方法的对象,就是一个可迭代对象。

    s = 'hello world'  # s可迭代对象
    i = s.__iter__()  # i迭代器
    print(i.__next__())  # 取出第一个值
    print(i.__next__())  # 取出第二个值
    print(i.__next__())  # 取出第三个值
    ...
    

      同样也可以通过iter()函数获取一个迭代器对象,然后使用next()函数来获取对象中的每一个数据。

    s = 'hello world'  # s可迭代对象
    i = iter(s)  # i迭代器
    print(next(i))  # 取出第一个值
    print(next(i))  # 取出第二个值
    print(next(i))  # 取出第三个值
    ...
    

      

           注意:当使用__next__()或next()取完最后一个值后,再调用__next__()或next()会报异常:StopIteration(停止迭代)

     

            4、普通类的可迭代

           普通的类不是可迭代对象,但类定义中重写了__iter__()魔法方法,类就变成了可迭代对象。

    from collections.abc import Iterable
    
    
    class Demo:
        def __init__(self, x):
            pass
    
        def __iter__(self):   # 重写方法
            pass
    
    
    d = Demo(1)
    print(isinstance(d, Iterable))   # True
    

      

           5、for...in的本质

           for...in的本质就是调用可迭代对象的__iter__方法,获得这个方法的返回值,这个返回值需要时一个对象,然后在调用这个对象的__next__方法。

    class Foo:
        def __next__(self):
            return [1, 2, 3]
    
    
    class Demo:
        def __init__(self):
            pass
    
        def __iter__(self):
            return Foo()
    
    
    d = Demo()
    for i in d:
        print(i)
    
    运行结果:
    [1, 2, 3]
    [1, 2, 3]
    [1, 2, 3]
    [1, 2, 3]
    [1, 2, 3]
    ...
    

      

           为了减少内存消耗,类也可以自己迭代自己,在类定义__next__方法。
    class Demo:
        def __init__(self, x):
            self.x = x
            self.count = 1
    
        def __iter__(self):  # 只要重写iter方法就是可迭代对象
            return self
    
        def __next__(self):  # 每一次for...in都会调用一次next方法,获取返回值
            if self.count <= self.x:
                self.count += 1
                return 'hello world'
            else:
                raise StopIteration  # 停止迭代
    
    
    d = Demo(4)
    for x in d:
        print(x)
    # hello world
    # hello world
    # hello world
    # hello world
    
    d.__iter__().__next__()  # 对象迭代简单化
    

      

           6、判断对象是不是迭代器

           调用一个对象的__iter__方法,或者调用iter()内置函数,可以获取到一个可迭代对象的迭代器。
    names=['zhangsan','lisi']
    print(names.__iter__())   #调用方法
    print(iter(names))    #调用函数
    

      

           可以使用isinstance()判断一个对象是否是Iterator对象。
    from collections.abc import Iterator
    names=['zhangsan','lisi']
    print(isinstance(iter(names), Iterator))  #True
    

      

     二、生成器

           利用迭代器,我们可以在每次迭代获取数据(通过next()方法)时按照特定的规律进行生成。但是我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,我们可以采用更简便的语法,即生成器(generator)。生成器是一类特殊的迭代器。

           1、创建生成器方法

           a、把列表推导式的【】改成()

    #列表推导式
    l = [x ** 2 for x in range(5)] print(l) # [0, 1, 4, 9, 16]

      

    #生成器
    g=(x**2 for x in range(5))
    print(g)   #<generator object <genexpr> at 0x0000000001D1FA20>
    print(next(g))   #0
    print(next(g))   #1
    print(next(g))   #4
    print(next(g))   #9
    print(next(g))   #16
    print(next(g))   #非正常退出
    
    #for x in g:
    #    print(x)
    #运行结果:
    #0
    #1
    #4
    #9
    #16
    

      

           创建l和g的区别在于最外层的[]和(),l是一个列表,而g是一个生成器。我们可以直接打印出列表l的每个元素,而对于生成器g,我们可以按照迭代器的使用方法来使用,即可以通过next()函数、for循环、list()等方法使用。

           b、使用函数实现

    def my_gen(n):
        i = 1
        while i < n:
            print('hello')
            yield i
            i += 1
            print('world')
    
    
    g = my_gen(4)
    print(next(g))
    print(next(g))
    print(next(g))
    print(next(g))
    # StopIteration
    # hello
    # 1
    # world
    # hello
    # 2
    # world
    # hello
    # 3
    # world
           在使用生成器实现的方式中,我们将原本在迭代器__next__方法中实现的基本逻辑放到一个函数中来实现,但是将每次迭代返回数值的return换成了yield,此时新定义的函数便不再是函数,而是一个生成器了。简单来说:只要在def中有yield关键字的就称为生成器。
     
           此时按照调用函数的方式(g=my_gen(5))使用生成器就不再是执行函数体了,而是会返回一个生成器对象(案例中为g),然后就可以按照使用迭代器的方式来使用生成器了。
     
           2、生成器示例
    def foo():
    print('starting...')
    while True:
    res = yield 'hello world'
    print('closing...',res)


    g = foo()
    print(next(g))
    print('*' * 10)
    print(next(g))

    # starting...
    # hello world
    # **********
    # closing... None
    # hello world
     
           生成器执行过程

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

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

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

           d、程序执行print("*"*10),输出10个*

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

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

           3、总结

    • 使用了yield关键字的函数不再是函数,而是生成器。(使用了yield的函数就是生成器)

    • yield关键字有两点作用:

               保存当前的运行状态(断点),然后暂停执行,即将生成器(函数)挂起

               将yield关键字后面表达式的值作为返回值返回,此时可以理解为起到了return的作用
    • 可以使用next()函数让生成器从断点处继续执行,即唤醒生成器(函数)

     
           4、使用send唤醒

           除了可以使用next()函数来唤醒生成器继续执行之外,还可以使用send()函数来唤醒执行,使用send()函数的一个好处是可以在唤醒的同时向断点处传入一个附加数据。

     
    def gen():
        i = 0
        while i < 5:
            temp = yield i
            print(temp)
            i += 1
    
    
    f = gen()
    print(next(f))
    f.send('hello')  # 唤醒生成器,并传值
    print(next(f))
    # 0 # hello # None # 2

      

  • 相关阅读:
    cqyz oj | 单峰排列
    cqyz oj/uva 548 | 二叉树
    cqyz oj | 树网的核 | 树的直径
    cqyz oj | 树上的询问 | 最近公共祖先
    cqyz oj | 循环逆序对 | 逆序对 | 树状数组
    cqyz oj | 潜水比赛 | 贪心
    YOLO v3 & Pascal VOC数据集
    太阳爆发分类
    PPT制作
    anaconda
  • 原文地址:https://www.cnblogs.com/shixiaoxun/p/14438521.html
Copyright © 2020-2023  润新知