14 - 生成器和迭代器

一、生成器

1.为什么要有生成器

  • 通过列表推导式,我们可以创建一个列表,但是受到内存限制,列表的容量肯定是受限的,不仅如此如果创建一个元素很多的列表,不仅占用很大的存储空间,而且我们有时只是访问前几个元素,那么后面绝大多数元素占用的空间都白白浪费了。

    所以如果列表元素可以按照某种算法推算出来,那么是否可以在循环中不断推算出后续的元素呢?这样就不必创建完整的列表,从而节省了大量空间。

2.生成器概念

  • 一边循环一边计算的机制,称为生成器--generator

3.创建生成器

  1. 通过列表推导式得到生成器:

    g = (x*3 for x in range(20))(格式和列表推导式类似,只需换成()即可)

    通过生成器创建列表,不会一下子开辟很多的空间,而是用一个元素就生成一个元素

  2. 通过函数定义生成器:

    只要函数中出现了yield关键字,说明函数就不是函数了,变成生成器了

    def func():
    	n = 0
        while True:
            n += 1
            yield n  #yield的作用:返回 n 并且暂停  (return n + 暂停),等下一次调用next(),再继续接着执行
    g = func()  #得到生成器g
    

    步骤:

    1. 定义一个函数,函数中使用yield关键字
    2. 调用函数,接收调用的结果
    3. 得到的结果就是生成器
    4. 借助于next(),或者__next__()得到元素

4.通过生成器生成元素的方法

  • g.__next__():每调用一次生成一个元素,指针会后移。生成器函数

  • next(g):每调用一次next则会产生一个元素。python内置函数

    如果超过了生成器可以生产的元素个数,会报错

    g = (x * 3 for x in range(10))
    #print(next(g)) #0
    #print(g.__next__()) #3
    #print(next(g)) #6
    
    while True:
    	try:
    		e = next(g)
    		print(e)
    	except:
    		print('没有更多元素了')
    		break
    
    def func():
    	n = 0
        while True:
            n += 1
            yield n  #yield的作用:返回 n 并且暂停  (return n + 暂停),等下一次调用next(),再接着顺序执行
    g = func()  #得到生成器g
    print(g.next()) #1
    print(g.next()) #2
    

6.生成器方法

  • __next__():获取下一个元素;如果用函数的方式定义的生成器,则获取yield返回的值

  • send(值):可以获取调用生成器中的值,也可以向每次生成器调用中传值,但注意:第一次要传的值必须是None

    ​ 什么时候使用呢?如果生成器生成值的时候需要外面往里面送值,就用send

    def gen():
    	i = 0
        while i < 2:
            temp = yield i
            print(temp)
            for x in range(temp):
                print('-->',x)
            print('******************')
            i += 1
        return '没有更多数据了'
    
    g = gen()
    print(g.send(None))
    print(g.send(3))
    print(g.send(5))
    
    '''
    0
    3
    --> 0
    --> 1
    --> 2
    ******************
    1
    5
    --> 0
    --> 1
    --> 2
    --> 3
    --> 4
    ******************
    Traceback (most recent call last):
      File "E:/PythonLearn/PycharmProjects/pythonProject/day01/test1.py", line 80, in <module>
        print(g.send(5))
    StopIteration: 没有更多数据了
    '''
    

    过程:将gen函数加载到内存中;g = gen()先用函数的方法创建生成器(不是调用函数);然后执行g.send(None),进入到gen生成器中,顺序执行语句...,到temp = yield i,程序先执行yield i 将i的值0返回并且暂停执行、temp还没有被赋值,因为暂停了。程序继续回到print(g.send(None))将返回的值0打印,接着进入下一行语句print(g.send(3)),send函数有参数3,所以会将3传入给刚才暂停的地方,而且恢复执行temp = yield i,此时将3赋值给temp,继续向下执行语句

    print(temp) ​ for i in range(temp): ​ print('-->',i) ​ print('******************') ​ i += 1

    然后继续循环执行到 temp = yield i,又是先执行yield i,返回i值并且暂停将返回值返回给print(g.send(3)),打印1;接着执行print(g.send(5)),然后send函数执行一次,就进入到生成器中暂停的地方去,将5赋给temp,接着继续执行,这次i = 2无满足循环条件了,就将return后面的值当做报错信息返回了。

    注意:为什么第一个send()函数参数要为None?因为send()执行时会进入生成器中,并且进参数传进去,但是第一次进入gen(),没有什么语句能接受这个传进去的值,就会报错

5.生成器的应用

  • 应用一:通过生成器生成斐波那契数列

    def fib(length):  #length表示斐波那契数列的长度
        a, b = 0, 1
        n = 0
        
        while n < length:
            yield b
            a, b = b, a + b
            n += 1
        return '没有更多元素了'  #如果生成器无法产生元素了,那么next就接收不到元素了,此时可以通过return将信息以报错的形式发送给next(),然后打印出来
    g = fib(10)
    while True:
    	print(next(g))
    '''
    结果为:
    1
    1
    2
    3
    5
    8
    13
    21
    34
    55
    Traceback (most recent call last):
      File "E:/PythonLearn/PycharmProjects/pythonProject/day01/test1.py", line 58, in <module>
        print(next(g))
    StopIteration: 没有更多元素了
    '''
    

一般用return 返回报错的提示信息

  • 应用二:多任务交替执行

    #因为生成器中的yield有暂停的作用,可以实现多任务交替执行
    def produce(n):
        for i in range(n):
            print('生产第{}'.format(i))
            yield None
    
    
    def consume(n):
        for i in range(n):
            print('消费第{}'.format(i))
            yield None
    
    g1 = produce(5)
    g2 = consume(5)
    
    while True:
        try:
            g1.__next__()
            g2.__next__()
        except:
            break
    '''
    生产第0
    消费第0
    生产第1
    消费第1
    生产第2
    消费第2
    生产第3
    消费第3
    生产第4
    消费第4
    '''
    

二、迭代器

1.可迭代对象

  • 1.生成器(每要一个元素就可以生成一个元素)

  • 2.元组、列表、字典、集合、字符串

  • 如何判断对象是否为可迭代的

    isinstance('abc',Iterable):将要判断的对象作为第一个参数,第二个参数为属于的类型

    需要导包:from collections import Iterable

2.迭代器概念

  • 每迭代一次(即在for...in...中每循环一次)都会返回对象中的下一条数据,一直向后读取数据直到迭代了所有数据后结束。那么,在这个过程中就应该有一个“人”去记录每次访问到了第几条数据,以便每次迭代都可以返回下一条数据。我们把这个能帮助我们进行数据迭代的“人”称为迭代器(Iterator)
  • 一个实现了iter方法和next方法的对象,就是迭代器。

3.迭代器的作用

  • 迭代器是用来帮助我们记录每次迭代访问到的位置,当我们对迭代器使用next()函数的时候,迭代器会向我们返回它所记录位置的下一个位置的数据。

4.迭代器生成器可迭代对象关系

  • 可迭代的对象是否一定是迭代器?

    • 生成器是可迭代的,也是迭代器
    • 列表等是可迭代的,但不是迭代器,因为调用next方法,列表没法一个一个依次得到下一个元素
  • 迭代器生成器可迭代对象的关系图

    image-20211010135052364

生成器是迭代器的一部分,可迭代对象通过iter()可转化为迭代器

5.如何将可迭代的对象变成迭代器

  • 通过内置函数iter()将可迭代的变成一个迭代器:list --> iter(list) --> 迭代器 可调用next(迭代器)或迭代器.__next__()迭代

  • 通过对象的__iter__()函数将可迭代对象变成迭代器:list -->list.__iter__() --> 迭代器 next()或__next__()

    names = ['hello', 'good', 'yes']
    it = names.__iter__()  # 调用对象的__iter__()方法
    #it = iter(names)  # 调用iter()内置函数
    print(it.__next__()) #hello
    print(it.__next__()) #good
    
  • 任意对象变成迭代器--自定义迭代器

    自定义迭代器需要重写__iter__和__next__方法

    class MyIterator(object):
        def __init__(self, n):
            self.n = n
            self.current = 0
    
        # 自定义迭代器需要重写__iter__和__next__方法
        def __iter__(self):
            return self
    
        def __next__(self):
            if self.current < self.n:
                value = self.current
                self.current += 1
                return value
            else:
                raise StopIteration
    
    my_it = MyIterator(10)
    
    for i in my_it:    # 迭代器重写了__iter__方法,它本身也是一个可迭代对象
        print(i)
    

6. for...in...循环的本质、

  • for item in Iterable 循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。
posted @ 2021-10-14 19:16  深藏Bluee  阅读(92)  评论(0)    收藏  举报