生成器和迭代器的联系与区别

生成器和迭代器

前言:

一对黏黏糊糊的好基友

一、生成器

记住一点,生成器的诞生是为了拯救内存的。

1、列表表达式秒变生成器
# 列表表达式
l = [i for i in range(10)]
print(l) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# shengqchengqi 
g = (i for i in range(10))
print(g) # <generator object <genexpr> at 0x119ef2cf0>
# 生成器取值
printnext(g)'一次next取一个值,当取完的时候,直接报错 StopIteration'
# 一次性全取。这种情况不会报错 StopIteration
for i in g:
    print(i)
2、函数生成器

简单来说就是在return换成yield,让函数不要一次性全部执行完毕,在yield出暂时暂停一下

举例:将斐波那契数列的函数式生成,改为生成器

# 100以内的斐波那契额数列函数
def fib(max):
    a,b = 0,1
    n = 0  # 斐波那契数
    while n < max:
        n = a + b
        a = b # 把b的旧值给到a
        b = n # 新的b = a + b(旧b的值)
        if n < max:
            print(n)
        #print(n if n < max else None)
# fib函数实际上是定义了斐波那契数列的推算规则,可以从第一个元素开始,推算出后续任意元素的元素,这是一种边执行边运算的逻辑,跟生成器非常类似,联想for + range的组合,就可对照出差异。

将上面的函数改成生成器,只需要将print(b)改写为yield(b)

def fib(max):
    a,b = 0,1
    n = 0  # 斐波那契数
    while n < max:
        n = a + b
        a = b # 把b的旧值给到a
        b = n # 新的b = a + b(旧b的值)
        if n < max:
          yield n
        #yield(n if n < max else None)
for i in fib_g(10):
    print(i)
# 输出
1
2
3
5
8
# 或者
g = fib_g(10)
print(next(g)) # 1
print(next(g)) # 2
print(next(g)) # 3
print(next(g)) # 5
print(g.__next__()) # 8

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句暂停并返回数据到函数外,再次被next()调用时从上次返回的yield语句处继续执行

3、补充一个基于yield生成器的并发
import time


def customer(name):
  
    while 1:
        n = yield # 执行一下next可以使上面的函数走到yield那句。 这样后面的send语法才能生效
        print(f'{name} eat {n}包子')


def func():
    try:
        g1 = customer('bob')
        g2 = customer('mike')
        g1.__next__()
        g2.__next__()
        print('开始做包子')
        for i in range(1, 10):
            print(f'做了{i}包子')
            time.sleep(1)
            g1.send(i)# send的作用=next, 同时还把数据传给了上面函数里的yield
            g2.send(i)
    except StopIteration as e:
        print(e)

if __name__ == '__main__':
    func()
'''
注意:调用send(x)给生成器传值时,必须确保生成器已经执行过一次next()调用, 这样会让程序走到yield位置等待外部第2次调用。
'''

二、迭代器(Iterator)

生成器约等于迭代器(不成文规矩)

可以被for循环的数据类型有:

1.一类是集合数据类型,如list、tuple、dict、set、str等;

2.一类是generator,包括生成器和带yield的generator function

这些可以直接作用于for循环的对象统称为可迭代对象:Iterable,可迭代的意思就是可遍历、可循环

杂货铺:collections

# 使用isinstance()判断一个对象是否是可迭代对象:
from collections import Iterable
isinstance([], Iterable) # True
isinstance({}, Iterable) # True
isinstance(100, Iterable) #False

而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。

# 判断是不是迭代器Iterator对象
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False
# 将可迭代对象改造为迭代器对象 iter()
> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
# eg:
g_l = iter([1,2,3])
print(g_l.__next__()) # 1
print(g_l.__next__()) # 2

注意:

为什么listdictstr等数据类型不是Iterator

这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

总结:

  1. 凡是可以用for循环的对象都是可迭代(Iterable)类型
  2. 凡是可以用next()调用取值的对象都是迭代器(Iterator)类型,表示一个惰性计算的徐磊
  3. 集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值