产生需求

任何一个功能的出现,必定是有其对应的需求的。

使用Python获取斐波拉契数列,斐波拉契数列(Fibonacci):除第一个数以外,所有的数字都是由前面两项相加得到。

def fib(n):
    """
    获取斐波拉契数列
    :param n: 需要获取自然数n前的所有斐波拉契数列中的数字
    :return: result,以列表的形式返回数列
    """
    a, b = 0, 1
    result = [a, b]

    while True:
        c = a + b
        if c <= n:
            result.append(c)
            a, b = b, c
        else:
            break

    return result


a = fib(100)  # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

问题

通过上面的函数我们可以获取数字n前面的所有斐波拉契数列,但是如果我使用函数fib()获取了100前的所有斐波拉契数列,但是我再使用的过程中却发现我只需要10以前的斐波拉契数列就行了。

这个时候,就产生了内存浪费,制造了100前的所有斐波拉契数列,但是你实际需求只用了10以前的。

抛出疑问

能不能写出这样一个函数,就是我所产生的斐波拉契数列个数是小于等于100前的,但是至于我用到哪些,现在是还不确定的,但是我可以随用随造,不用10以后的斐波拉契数列的话,那么这个函数就不会制造后面的呢?

解决办法

这个时候,就用到了生成器。

结合列表推导式,把其两端的[]换成()即可得到生成器,一个简单的举例:

a = (i for i in range(3))

与列表推导式不同的是,列表生成器在创建的时候直接生成了一个列表:[0, 1, 2],而生成器则是创建了一个生成器对象,只有在后续调用的时候才会生成其中的元素。

b = [i for i in range(3)]
print(a)  # <generator object <genexpr> at 0x000001A7767A5690>
print(b)  # [0, 1, 2]

如何使用生成器呢?生成器每次使用都需要进行调用,且生成的值只能使用一次,之后就产生下一个元素:

print(a.__next__())  # 0
print(a.__next__())  # 1
print(next(a))  # 2
print(a.__next__())  # 报错:StopIteration

如果超出生成器中元素个数,就会报错。

上述逐个调用生成器中的元素的话,如果超出则会报错,但是使用for循环的话就不会报错:

for i in a:
    print(i)  #0, 1, 2

其实Python中很多地方都有生成器的使用场景,例如使用open()函数对文件执行逐行读取的时候,就是每次读取一行,不读就不取。

yield关键字

回到最初提出的问题,如何实现函数中也实现生成器的效果呢?这个时候就需要使用yield关键字。

def fib(n):
    a, b = 0, 1
    i = 0
    while True:
        i += 1
        print(f'开始第{i}次循环...')
        c = a + b
        if c <= n:
            yield c
            a, b = b, c
        else:
            break
        print(f'第{i}次循环结束...')


# 正常函数在创建的时候就会运行完毕,但是加入yield关键字的函数,在创建的时候,运行到yield关键字的位置就会停止,并在第一次调用.__next__()方法的时候将yield关键字后面的变量返回出来,然后继续执行后续代码,直至再次遇见yield。
a = fib(100)  # [1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

while True:
    try:
        print(a.__next__())
    except Exception as e:
        print('已打印完所有元素...', e)
        break