迭代器和生成器

# 最简单的生成器
l1 = [2, 4, 5, 6]
# 利用生成器实现生成一个新列表,元素等于l内的每个元素+1
l2 = [x+1 for x in l1]
print(type(l2))  # 结果:<class 'list'>
print(l2)  # 结果:[3, 5, 6, 7]

# 因为生成器会根据逻辑遍历可迭代对象(此处是l1)生成新的对象。这本身没什么问题,也符合我们的要求
# 但是如果可迭代对象的内容非常庞大例如:

# l3 = [x+1 for x in range(1000000)]
# print(l3)  # 直接生成新的对象后,存储下来。而对象在接下来会被调用,所以会保存在内存中

# 通过上面的例子,我们知道直接使用生成器如果遇到较大的迭代对象,就会占用非常大的内存

 

# 下面通过该为迭代器的方法来实现上述例子

# l4 = (x+1 for x in range(1000000))
# print(l4)  # 结果是生成器:<generator object <genexpr> at 0x0000000001ED23C8>
# 通过next(生成器)取值,取到最后一个值的时候,再次取值会报错
# l5 = (x+1 for x in l1)
# print(next(l5))  # 结果是:3
# print(next(l5))  # 结果是:5
# print(next(l5))  # 结果是:6
# print(next(l5))  # 结果是:7
# print(next(l5))  # 此处生成器中已经没有多余的元素可以取了,所以会抛异常StopIteration,结果如下:
"""
Traceback (most recent call last):
  File "D:/02Project/pacho/15day/st_迭代.py", line 26, in <module>
    print(next(l5))
StopIteration
"""

 

# 为了避免此类情况的发生,可以采用for循环的的方式获取生成器的值,不会抛出异常,例子如下:

l6 = (x for x in l1)
print(type(l6))  # 结果是:<class 'generator'>
for i in l6: print(i)  # 结果是  2  4  5  6

# 插入:偶然发现了一个错误的写法

l7 = (x for x in l1)
for i in l7: print(next(l7))  # 结果是  4  6  原因是 for循环已经在调用next方法了,所以再次print(next(l7))实际是再次取一次值

 

# 好了步入正题了,下面看下典型应用——斐波那契数列
# 这里写一个生成斐波那契数列的函数

def fei(n):  # 输出前n个数
    x, y, z = 0, 0, 1
    while True:
        if x < n:
            print(z)
            y, z = z, y + z
            x += 1
        else:
            break
    return 'this is res'


# 运行一下看看
fei(5)  # 结果是:  1  1  2  3  5
fei(8)  # 结果是:  1  1  2  3  5  8  13  21

 

# 现在我们不想让他一下输入所有结果,想让他每次调用输出一个结果(不要说什么根据递归重新写个方法,虽然也可行。。。)

f = fei(5)  # 运行函数fei(5)  输出结果:  1  1  2  3  5 并把函数的返回值'this is res'赋值给f
print(type(f))  # 结果是:<class 'str'>
print(f)  # 结果是:this is res

# 显然上面的操作并没有实现每次取他输出结果的功能,那么我们改一下上述函数,是他由一个普通函数变成生成器

def fei_1(n):  # 输出前n个数
    x, y, z = 0, 0, 1
    while True:
        if x < n:
            yield z  # 只是在这里吧输出改成了yield
            y, z = z, y + z
            x += 1
        else:
            break
    return 'this is res'

# 再看下输出结果
f = fei_1(5)  # 运行函数fei_1(5)  没有输出结果,而是把函数变成了一个生成器
print(f)  # 结果是:<generator object fei_1 at 0x0000000001EA23C8>  可以看到我们得到了一个生成器
print(type(f))  # 结果是:<class 'generator'>
print(next(f))  # 结果是:1

 

# 我们写个简单的来看下
def f(n):
    for i in range(n):
        print('for start ...' if i == 0 else 'start start start')
        yield i
        print('for end ...' if i == n - 1 else 'end end end')
    return 'done'
#
n = f(3)  # 得到了一个生成器
print(type(n))  # 结果:<class 'generator'>
print(n)  # 结果:<generator object f at 0x0000000001EA23C8>
print(next(n))  # 结果:【for start ...】  【0】
print(next(n))  # 结果:【end end end】  【start start start】 【1】
print(next(n))  # 结果:【end end end】  【start start start】 【2】 【for end ...】
print(next(n))  # 生成器中没有元素,抛异常,导致一个尴尬的结果就是没有获取到函数f的返回值
"""
Traceback (most recent call last):
  File "D:/02Project/pacho/15day/st_迭代.py", line 101, in <module>
    print(next(n))  # 结果:【end end end】  【start start start】 【1】
StopIteration: done
"""

 

# 从上面的例子我们就能够知道yield关键字生成器的作用:
# 1、通过yield关键字把一个函数变成生成器
# 2、每次调用会执行一次函数,并且执行到yield关键字后暂停
# 3、再次调用会从yield之后的代码开始执行
# 4、当生成器执行结束之后,才会返回函数的返回值

# 下面换一种能够获取到函数返回值的方法

gen = f(3)
print(type(gen))
while True:  # 循环取生成器的元素
    try:
        i = next(gen)  # 取生成器的元素
        print(i)
    except StopIteration as e:  # 当取不到的时候,捕获异常,并得到生成器函数的返回值
        print(e)
        break
# 结果是
"""
<class 'generator'>
for start ...
0
end end end
start start start
1
end end end
start start start
2
for end ...
done
"""

 

# 好了,现在或过头来看自定义的生成器函数fei(生成斐波那契额数列,并且有返回值),该怎么调用呢,和上面一样

ff = fei_1(6)
while True:
    try:
        print(next(ff))
    except StopIteration as e:  # 顺便加深理解了一下捕捉异常:如果调用某个生成器函数,捕获得到的异常e和e.value均为该函数的返回值
        print(e)
        print(e.value)
        break

# 结果如下:
"""
1
1
2
3
5
8
this is res
this is res
"""

 

参考:

前人种树后人乘凉,感谢分享:https://www.cnblogs.com/wj-1314/p/8490822.html

posted @ 2019-10-12 12:02  唐大侠的小迷弟  阅读(130)  评论(0编辑  收藏  举报